difftreelog
tests(nesting): revert majority of last commit + refactor token and collection naming convention
in: master
6 files changed
tests/src/fungible.test.tsdiffbeforeafterboth--- a/tests/src/fungible.test.ts
+++ b/tests/src/fungible.test.ts
@@ -15,7 +15,9 @@
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
import {IKeyringPair} from '@polkadot/types/types';
-import {itSub, usingPlaygrounds, expect, U128_MAX} from './util/playgrounds';
+import {itSub, usingPlaygrounds, expect} from './util/playgrounds';
+
+const U128_MAX = (1n << 128n) - 1n;
describe('integration test: Fungible functionality:', () => {
let alice: IKeyringPair;
tests/src/nesting/graphs.test.tsdiffbeforeafterboth--- a/tests/src/nesting/graphs.test.ts
+++ b/tests/src/nesting/graphs.test.ts
@@ -16,8 +16,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {expect, itSub, usingPlaygrounds} from '../util/playgrounds';
-import {ITokenNonfungible} from '../util/playgrounds/types';
-import {UniqueHelper} from '../util/playgrounds/unique';
+import {UniqueHelper, UniqueNFToken} from '../util/playgrounds/unique';
/**
* ```dot
@@ -26,7 +25,7 @@
* 8 -> 5
* ```
*/
-async function buildComplexObjectGraph(helper: UniqueHelper, sender: IKeyringPair): Promise<ITokenNonfungible[]> {
+async function buildComplexObjectGraph(helper: UniqueHelper, sender: IKeyringPair): Promise<UniqueNFToken[]> {
const collection = await helper.nft.mintCollection(sender, {permissions: {nesting: {tokenOwner: true}}});
const tokens = await collection.mintMultipleTokens(sender, Array(8).fill({owner: {Substrate: sender.address}}));
tests/src/nesting/properties.test.tsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '../util/playgrounds';19import {ICollectionBase, ICollectionNFT, ITokenNonfungible, ICollectionRFT, ITokenRefungible} from '../util/playgrounds/types';20import {UniqueHelper} from '../util/playgrounds/unique';2122// ---------- COLLECTION PROPERTIES2324describe('Integration Test: Collection Properties', () => {25 let alice: IKeyringPair;26 let bob: IKeyringPair;2728 before(async () => {29 await usingPlaygrounds(async (helper, privateKey) => {30 const donor = privateKey('//Alice');31 [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);32 });33 });3435 itSub('Properties are initially empty', async ({helper}) => {36 const collection = await helper.nft.mintCollection(alice);37 expect(await collection.getProperties()).to.be.empty;38 });3940 async function testSetsPropertiesForCollection(collection: ICollectionBase) {41 // As owner42 await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}])).to.be.fulfilled;4344 await collection.addAdmin(alice, {Substrate: bob.address});4546 // As administrator47 await expect(collection.setProperties(bob, [{key: 'black_hole'}])).to.be.fulfilled;4849 const properties = await collection.getProperties();50 expect(properties).to.include.deep.members([51 {key: 'electron', value: 'come bond'},52 {key: 'black_hole', value: ''},53 ]);54 }5556 itSub('Sets properties for a NFT collection', async ({helper}) => {57 await testSetsPropertiesForCollection(await helper.nft.mintCollection(alice));58 });5960 itSub.ifWithPallets('Sets properties for a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {61 await testSetsPropertiesForCollection(await helper.rft.mintCollection(alice));62 });6364 async function testCheckValidNames(collection: ICollectionBase) {65 // alpha symbols66 await expect(collection.setProperties(alice, [{key: 'answer'}])).to.be.fulfilled;6768 // numeric symbols69 await expect(collection.setProperties(alice, [{key: '451'}])).to.be.fulfilled;7071 // underscore symbol72 await expect(collection.setProperties(alice, [{key: 'black_hole'}])).to.be.fulfilled;7374 // dash symbol75 await expect(collection.setProperties(alice, [{key: '-'}])).to.be.fulfilled;7677 // dot symbol78 await expect(collection.setProperties(alice, [{key: 'once.in.a.long.long.while...', value: 'you get a little lost'}])).to.be.fulfilled;7980 const properties = await collection.getProperties();81 expect(properties).to.include.deep.members([82 {key: 'answer', value: ''},83 {key: '451', value: ''},84 {key: 'black_hole', value: ''},85 {key: '-', value: ''},86 {key: 'once.in.a.long.long.while...', value: 'you get a little lost'},87 ]);88 }8990 itSub('Check valid names for NFT collection properties keys', async ({helper}) => {91 await testCheckValidNames(await helper.nft.mintCollection(alice));92 });9394 itSub.ifWithPallets('Check valid names for ReFungible collection properties keys', [Pallets.ReFungible], async ({helper}) => {95 await testCheckValidNames(await helper.rft.mintCollection(alice));96 });9798 async function testChangesProperties(collection: ICollectionBase) {99 await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: ''}])).to.be.fulfilled;100101 // Mutate the properties102 await expect(collection.setProperties(alice, [{key: 'black_hole', value: 'LIGO'}])).to.be.fulfilled;103104 const properties = await collection.getProperties();105 expect(properties).to.include.deep.members([106 {key: 'electron', value: 'come bond'},107 {key: 'black_hole', value: 'LIGO'},108 ]);109 }110111 itSub('Changes properties of a NFT collection', async ({helper}) => {112 await testChangesProperties(await helper.nft.mintCollection(alice));113 });114115 itSub.ifWithPallets('Changes properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {116 await testChangesProperties(await helper.rft.mintCollection(alice));117 });118119 async function testDeleteProperties(collection: ICollectionBase) {120 await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}])).to.be.fulfilled;121122 await expect(collection.deleteProperties(alice, ['electron'])).to.be.fulfilled;123124 const properties = await collection.getProperties(['black_hole', 'electron']);125 expect(properties).to.be.deep.equal([126 {key: 'black_hole', value: 'LIGO'},127 ]);128 }129130 itSub('Deletes properties of a NFT collection', async ({helper}) => {131 await testDeleteProperties(await helper.nft.mintCollection(alice));132 });133134 itSub.ifWithPallets('Deletes properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {135 await testDeleteProperties(await helper.rft.mintCollection(alice));136 });137});138139describe('Negative Integration Test: Collection Properties', () => {140 let alice: IKeyringPair;141 let bob: IKeyringPair;142143 before(async () => {144 await usingPlaygrounds(async (helper, privateKey) => {145 const donor = privateKey('//Alice');146 [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor);147 });148 });149 150 async function testFailsSetPropertiesIfNotOwnerOrAdmin(collection: ICollectionBase) { 151 await expect(collection.setProperties(bob, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]))152 .to.be.rejectedWith(/common\.NoPermission/);153154 expect(await collection.getProperties()).to.be.empty;155 }156157 itSub('Fails to set properties in a NFT collection if not its onwer/administrator', async ({helper}) => {158 await testFailsSetPropertiesIfNotOwnerOrAdmin(await helper.nft.mintCollection(alice));159 });160161 itSub.ifWithPallets('Fails to set properties in a ReFungible collection if not its onwer/administrator', [Pallets.ReFungible], async ({helper}) => {162 await testFailsSetPropertiesIfNotOwnerOrAdmin(await helper.rft.mintCollection(alice));163 });164 165 async function testFailsSetPropertiesThatExeedLimits(collection: ICollectionBase) {166 const spaceLimit = (await (collection.helper!.api! as any).query.common.collectionProperties(collection.collectionId)).spaceLimit.toNumber();167 168 // Mute the general tx parsing error, too many bytes to process169 {170 console.error = () => {};171 await expect(collection.setProperties(alice, [172 {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 9))},173 ])).to.be.rejected;174 }175176 expect(await collection.getProperties(['electron'])).to.be.empty;177178 await expect(collection.setProperties(alice, [179 {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 18))}, 180 {key: 'black_hole', value: '0'.repeat(Math.ceil(spaceLimit! / 2))}, 181 ])).to.be.rejectedWith(/common\.NoSpaceForProperty/);182183 expect(await collection.getProperties(['electron', 'black_hole'])).to.be.empty;184 }185186 itSub('Fails to set properties that exceed the limits (NFT)', async ({helper}) => {187 await testFailsSetPropertiesThatExeedLimits(await helper.nft.mintCollection(alice));188 });189190 itSub.ifWithPallets('Fails to set properties that exceed the limits (ReFungible)', [Pallets.ReFungible], async ({helper}) => {191 await testFailsSetPropertiesThatExeedLimits(await helper.rft.mintCollection(alice));192 });193 194 async function testFailsSetMorePropertiesThanAllowed(collection: ICollectionBase) {195 const propertiesToBeSet = [];196 for (let i = 0; i < 65; i++) {197 propertiesToBeSet.push({198 key: 'electron_' + i,199 value: Math.random() > 0.5 ? 'high' : 'low',200 });201 }202203 await expect(collection.setProperties(alice, propertiesToBeSet)).204 to.be.rejectedWith(/common\.PropertyLimitReached/);205206 expect(await collection.getProperties()).to.be.empty;207 }208209 itSub('Fails to set more properties than it is allowed (NFT)', async ({helper}) => {210 await testFailsSetMorePropertiesThanAllowed(await helper.nft.mintCollection(alice));211 });212213 itSub.ifWithPallets('Fails to set more properties than it is allowed (ReFungible)', [Pallets.ReFungible], async ({helper}) => {214 await testFailsSetMorePropertiesThanAllowed(await helper.rft.mintCollection(alice));215 });216 217 async function testFailsSetPropertiesWithInvalidNames(collection: ICollectionBase) {218 const invalidProperties = [219 [{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}],220 [{key: 'Mr/Sandman', value: 'Bring me a gene'}],221 [{key: 'déjà vu', value: 'hmm...'}],222 ];223224 for (let i = 0; i < invalidProperties.length; i++) {225 await expect(226 collection.setProperties(alice, invalidProperties[i]), 227 `on rejecting the new badly-named property #${i}`,228 ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);229 }230231 await expect(232 collection.setProperties(alice, [{key: '', value: 'nothing must not exist'}]), 233 'on rejecting an unnamed property',234 ).to.be.rejectedWith(/common\.EmptyPropertyKey/);235236 await expect(237 collection.setProperties(alice, [{key: 'CRISPR-Cas9', value: 'rewriting nature!'}]), 238 'on setting the correctly-but-still-badly-named property',239 ).to.be.fulfilled;240241 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat('CRISPR-Cas9').concat('');242243 const properties = await collection.getProperties(keys);244 expect(properties).to.be.deep.equal([245 {key: 'CRISPR-Cas9', value: 'rewriting nature!'},246 ]);247248 for (let i = 0; i < invalidProperties.length; i++) {249 await expect(250 collection.deleteProperties(alice, invalidProperties[i].map(propertySet => propertySet.key)), 251 `on trying to delete the non-existent badly-named property #${i}`,252 ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);253 }254 }255256 itSub('Fails to set properties with invalid names (NFT)', async ({helper}) => {257 await testFailsSetPropertiesWithInvalidNames(await helper.nft.mintCollection(alice));258 });259260 itSub.ifWithPallets('Fails to set properties with invalid names (ReFungible)', [Pallets.ReFungible], async ({helper}) => {261 await testFailsSetPropertiesWithInvalidNames(await helper.rft.mintCollection(alice));262 });263});264265// ---------- ACCESS RIGHTS266267describe('Integration Test: Access Rights to Token Properties', () => {268 let alice: IKeyringPair;269 let bob: IKeyringPair;270271 before(async () => {272 await usingPlaygrounds(async (helper, privateKey) => {273 const donor = privateKey('//Alice');274 [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor);275 });276 });277 278 itSub('Reads access rights to properties of a collection', async ({helper}) => {279 const collection = await helper.nft.mintCollection(alice);280 const propertyRights = (await helper.api!.query.common.collectionPropertyPermissions(collection.collectionId)).toJSON();281 expect(propertyRights).to.be.empty;282 });283 284 async function testSetsAccessRightsToProperties(collection: ICollectionNFT | ICollectionRFT) { 285 await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: true}}]))286 .to.be.fulfilled;287288 await collection.addAdmin(alice, {Substrate: bob.address});289290 await expect(collection.setTokenPropertyPermissions(bob, [{key: 'mindgame', permission: {collectionAdmin: true, tokenOwner: false}}]))291 .to.be.fulfilled;292293 const propertyRights = await collection.getPropertyPermissions(['skullduggery', 'mindgame']);294 expect(propertyRights).to.include.deep.members([295 {key: 'skullduggery', permission: {mutable: true, collectionAdmin: false, tokenOwner: false}},296 {key: 'mindgame', permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},297 ]);298 }299300 itSub('Sets access rights to properties of a collection (NFT)', async ({helper}) => {301 await testSetsAccessRightsToProperties(await helper.nft.mintCollection(alice));302 });303304 itSub.ifWithPallets('Sets access rights to properties of a collection (ReFungible)', [Pallets.ReFungible], async ({helper}) => {305 await testSetsAccessRightsToProperties(await helper.rft.mintCollection(alice));306 });307 308 async function testChangesAccessRightsToProperty(collection: ICollectionNFT | ICollectionRFT) {309 await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: true, collectionAdmin: true}}]))310 .to.be.fulfilled;311312 await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]))313 .to.be.fulfilled;314315 const propertyRights = await collection.getPropertyPermissions();316 expect(propertyRights).to.be.deep.equal([317 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},318 ]);319 }320321 itSub('Changes access rights to properties of a NFT collection', async ({helper}) => {322 await testChangesAccessRightsToProperty(await helper.nft.mintCollection(alice));323 });324325 itSub.ifWithPallets('Changes access rights to properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {326 await testChangesAccessRightsToProperty(await helper.rft.mintCollection(alice));327 });328});329330describe('Negative Integration Test: Access Rights to Token Properties', () => {331 let alice: IKeyringPair;332 let bob: IKeyringPair;333334 before(async () => {335 await usingPlaygrounds(async (helper, privateKey) => {336 const donor = privateKey('//Alice');337 [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);338 });339 });340341 async function testPreventsFromSettingAccessRightsNotAdminOrOwner(collection: ICollectionNFT | ICollectionRFT) {342 await expect(collection.setTokenPropertyPermissions(bob, [{key: 'skullduggery', permission: {mutable: true, tokenOwner: true}}]))343 .to.be.rejectedWith(/common\.NoPermission/);344345 const propertyRights = await collection.getPropertyPermissions(['skullduggery']);346 expect(propertyRights).to.be.empty;347 }348349 itSub('Prevents from setting access rights to properties of a NFT collection if not an onwer/admin', async ({helper}) => {350 await testPreventsFromSettingAccessRightsNotAdminOrOwner(await helper.nft.mintCollection(alice));351 });352353 itSub.ifWithPallets('Prevents from setting access rights to properties of a ReFungible collection if not an onwer/admin', [Pallets.ReFungible], async ({helper}) => {354 await testPreventsFromSettingAccessRightsNotAdminOrOwner(await helper.rft.mintCollection(alice));355 });356357 async function testPreventFromAddingTooManyPossibleProperties(collection: ICollectionNFT | ICollectionRFT) { 358 const constitution = [];359 for (let i = 0; i < 65; i++) {360 constitution.push({361 key: 'property_' + i,362 permission: Math.random() > 0.5 ? {mutable: true, collectionAdmin: true, tokenOwner: true} : {},363 });364 }365366 await expect(collection.setTokenPropertyPermissions(alice, constitution))367 .to.be.rejectedWith(/common\.PropertyLimitReached/);368369 const propertyRights = await collection.getPropertyPermissions();370 expect(propertyRights).to.be.empty;371 }372373 itSub('Prevents from adding too many possible properties (NFT)', async ({helper}) => {374 await testPreventFromAddingTooManyPossibleProperties(await helper.nft.mintCollection(alice));375 });376377 itSub.ifWithPallets('Prevents from adding too many possible properties (ReFungible)', [Pallets.ReFungible], async ({helper}) => {378 await testPreventFromAddingTooManyPossibleProperties(await helper.rft.mintCollection(alice));379 });380381 async function testPreventAccessRightsModifiedIfConstant(collection: ICollectionNFT | ICollectionRFT) {382 await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]))383 .to.be.fulfilled;384385 await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {collectionAdmin: true}}]))386 .to.be.rejectedWith(/common\.NoPermission/);387388 const propertyRights = await collection.getPropertyPermissions(['skullduggery']);389 expect(propertyRights).to.deep.equal([390 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},391 ]);392 }393394 itSub('Prevents access rights to be modified if constant (NFT)', async ({helper}) => {395 await testPreventAccessRightsModifiedIfConstant(await helper.nft.mintCollection(alice));396 });397398 itSub.ifWithPallets('Prevents access rights to be modified if constant (ReFungible)', [Pallets.ReFungible], async ({helper}) => {399 await testPreventAccessRightsModifiedIfConstant(await helper.rft.mintCollection(alice));400 });401402 async function testPreventsAddingPropertiesWithInvalidNames(collection: ICollectionNFT | ICollectionRFT) {403 const invalidProperties = [404 [{key: 'skullduggery', permission: {tokenOwner: true}}, {key: 'im possible', permission: {collectionAdmin: true}}],405 [{key: 'G#4', permission: {tokenOwner: true}}],406 [{key: 'HÆMILTON', permission: {mutable: false, collectionAdmin: true, tokenOwner: true}}],407 ];408409 for (let i = 0; i < invalidProperties.length; i++) {410 await expect(411 collection.setTokenPropertyPermissions(alice, invalidProperties[i]), 412 `on setting the new badly-named property #${i}`,413 ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);414 }415416 await expect(417 collection.setTokenPropertyPermissions(alice, [{key: '', permission: {}}]), 418 'on rejecting an unnamed property',419 ).to.be.rejectedWith(/common\.EmptyPropertyKey/);420421 const correctKey = '--0x03116e387820CA05'; // PolkadotJS would parse this as an already encoded hex-string422 await expect(423 collection.setTokenPropertyPermissions(alice, [424 {key: correctKey, permission: {collectionAdmin: true}},425 ]), 426 'on setting the correctly-but-still-badly-named property',427 ).to.be.fulfilled;428429 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat(correctKey).concat('');430431 const propertyRights = await collection.getPropertyPermissions(keys);432 expect(propertyRights).to.be.deep.equal([433 {key: correctKey, permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},434 ]);435 }436437 itSub('Prevents adding properties with invalid names (NFT)', async ({helper}) => {438 await testPreventsAddingPropertiesWithInvalidNames(await helper.nft.mintCollection(alice));439 });440441 itSub.ifWithPallets('Prevents adding properties with invalid names (ReFungible)', [Pallets.ReFungible], async ({helper}) => {442 await testPreventsAddingPropertiesWithInvalidNames(await helper.rft.mintCollection(alice));443 });444});445446// ---------- TOKEN PROPERTIES447448describe('Integration Test: Token Properties', () => {449 let alice: IKeyringPair; // collection owner450 let bob: IKeyringPair; // collection admin451 let charlie: IKeyringPair; // token owner452453 let permissions: {permission: any, signers: IKeyringPair[]}[];454455 before(async () => {456 await usingPlaygrounds(async (helper, privateKey) => {457 const donor = privateKey('//Alice');458 [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);459 });460461 // todo:playgrounds probably separate these tests later462 permissions = [463 {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob]},464 {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob]},465 {permission: {mutable: true, tokenOwner: true}, signers: [charlie]},466 {permission: {mutable: false, tokenOwner: true}, signers: [charlie]},467 {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},468 {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},469 ];470 });471 472 async function testReadsYetEmptyProperties(token: ITokenNonfungible | ITokenRefungible) {473 const properties = await token.getProperties();474 expect(properties).to.be.empty;475476 const tokenData = await token.getData();477 expect(tokenData!.properties).to.be.empty;478 }479480 itSub('Reads yet empty properties of a token (NFT)', async ({helper}) => {481 const collection = await helper.nft.mintCollection(alice);482 const token = await collection.mintToken(alice);483 await testReadsYetEmptyProperties(token);484 });485486 itSub.ifWithPallets('Reads yet empty properties of a token (ReFungible)', [Pallets.ReFungible], async ({helper}) => {487 const collection = await helper.rft.mintCollection(alice);488 const token = await collection.mintToken(alice);489 await testReadsYetEmptyProperties(token);490 });491492 async function testAssignPropertiesAccordingToPermissions(token: ITokenNonfungible | ITokenRefungible, pieces: bigint) {493 await token.collection.addAdmin(alice, {Substrate: bob.address});494 await token.transfer(alice, {Substrate: charlie.address}, pieces);495496 const propertyKeys: string[] = [];497 let i = 0;498 for (const permission of permissions) {499 i++;500 let j = 0;501 for (const signer of permission.signers) {502 j++;503 const key = i + '_' + signer.address;504 propertyKeys.push(key);505506 await expect(507 token.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 508 `on setting permission #${i} by alice`,509 ).to.be.fulfilled;510511 await expect(512 token.setProperties(signer, [{key: key, value: 'Serotonin increase'}]), 513 `on adding property #${i} by signer #${j}`,514 ).to.be.fulfilled;515 }516 }517518 const properties = await token.getProperties(propertyKeys);519 const tokenData = await token.getData();520 for (let i = 0; i < properties.length; i++) {521 expect(properties[i].value).to.be.equal('Serotonin increase');522 expect(tokenData!.properties[i].value).to.be.equal('Serotonin increase');523 }524 }525526 itSub('Assigns properties to a token according to permissions (NFT)', async ({helper}) => {527 const collection = await helper.nft.mintCollection(alice);528 const token = await collection.mintToken(alice);529 await testAssignPropertiesAccordingToPermissions(token, 1n);530 });531532 itSub.ifWithPallets('Assigns properties to a token according to permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {533 const collection = await helper.rft.mintCollection(alice);534 const token = await collection.mintToken(alice, 100n);535 await testAssignPropertiesAccordingToPermissions(token, 100n);536 });537538 async function testChangesPropertiesAccordingPermission(token: ITokenNonfungible | ITokenRefungible, pieces: bigint) {539 await token.collection.addAdmin(alice, {Substrate: bob.address});540 await token.transfer(alice, {Substrate: charlie.address}, pieces);541542 const propertyKeys: string[] = [];543 let i = 0;544 for (const permission of permissions) {545 i++;546 if (!permission.permission.mutable) continue;547 548 let j = 0;549 for (const signer of permission.signers) {550 j++;551 const key = i + '_' + signer.address;552 propertyKeys.push(key);553554 await expect(555 token.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 556 `on setting permission #${i} by alice`,557 ).to.be.fulfilled;558559 await expect(560 token.setProperties(signer, [{key, value: 'Serotonin increase'}]), 561 `on adding property #${i} by signer #${j}`,562 ).to.be.fulfilled;563564 await expect(565 token.setProperties(signer, [{key, value: 'Serotonin stable'}]), 566 `on changing property #${i} by signer #${j}`,567 ).to.be.fulfilled;568 }569 }570571 const properties = await token.getProperties(propertyKeys);572 const tokenData = await token.getData();573 for (let i = 0; i < properties.length; i++) {574 expect(properties[i].value).to.be.equal('Serotonin stable');575 expect(tokenData!.properties[i].value).to.be.equal('Serotonin stable');576 }577 }578579 itSub('Changes properties of a token according to permissions (NFT)', async ({helper}) => {580 const collection = await helper.nft.mintCollection(alice);581 const token = await collection.mintToken(alice);582 await testChangesPropertiesAccordingPermission(token, 1n);583 });584585 itSub.ifWithPallets('Changes properties of a token according to permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {586 const collection = await helper.rft.mintCollection(alice);587 const token = await collection.mintToken(alice, 100n);588 await testChangesPropertiesAccordingPermission(token, 100n);589 });590591 async function testDeletePropertiesAccordingPermission(token: ITokenNonfungible | ITokenRefungible, pieces: bigint) {592 await token.collection.addAdmin(alice, {Substrate: bob.address});593 await token.transfer(alice, {Substrate: charlie.address}, pieces);594595 const propertyKeys: string[] = [];596 let i = 0;597598 for (const permission of permissions) {599 i++;600 if (!permission.permission.mutable) continue;601 602 let j = 0;603 for (const signer of permission.signers) {604 j++;605 const key = i + '_' + signer.address;606 propertyKeys.push(key);607608 await expect(609 token.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 610 `on setting permission #${i} by alice`,611 ).to.be.fulfilled;612613 await expect(614 token.setProperties(signer, [{key, value: 'Serotonin increase'}]), 615 `on adding property #${i} by signer #${j}`,616 ).to.be.fulfilled;617618 await expect(619 token.deleteProperties(signer, [key]), 620 `on deleting property #${i} by signer #${j}`,621 ).to.be.fulfilled;622 }623 }624625 expect(await token.getProperties(propertyKeys)).to.be.empty;626 expect((await token.getData())!.properties).to.be.empty;627 }628 629 itSub('Deletes properties of a token according to permissions (NFT)', async ({helper}) => {630 const collection = await helper.nft.mintCollection(alice);631 const token = await collection.mintToken(alice);632 await testDeletePropertiesAccordingPermission(token, 1n);633 });634635 itSub.ifWithPallets('Deletes properties of a token according to permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {636 const collection = await helper.rft.mintCollection(alice);637 const token = await collection.mintToken(alice, 100n);638 await testDeletePropertiesAccordingPermission(token, 100n);639 });640641 itSub('Assigns properties to a nested token according to permissions', async ({helper}) => {642 const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});643 const collectionB = await helper.nft.mintCollection(alice);644 const targetToken = await collectionA.mintToken(alice);645 const nestedToken = await collectionB.mintToken(alice, targetToken.nestingAccount());646647 await collectionB.addAdmin(alice, {Substrate: bob.address});648 await targetToken.transfer(alice, {Substrate: charlie.address});649650 const propertyKeys: string[] = [];651 let i = 0;652 for (const permission of permissions) {653 i++;654 let j = 0;655 for (const signer of permission.signers) {656 j++;657 const key = i + '_' + signer.address;658 propertyKeys.push(key);659 660 await expect(661 nestedToken.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 662 `on setting permission #${i} by alice`,663 ).to.be.fulfilled;664665 await expect(666 nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), 667 `on adding property #${i} by signer #${j}`,668 ).to.be.fulfilled;669 }670671 }672673 const properties = await nestedToken.getProperties(propertyKeys);674 const tokenData = await nestedToken.getData();675 for (let i = 0; i < properties.length; i++) {676 expect(properties[i].value).to.be.equal('Serotonin increase');677 expect(tokenData!.properties[i].value).to.be.equal('Serotonin increase');678 }679 expect(await targetToken.getProperties()).to.be.empty;680 });681682 itSub('Changes properties of a nested token according to permissions', async ({helper}) => {683 const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});684 const collectionB = await helper.nft.mintCollection(alice);685 const targetToken = await collectionA.mintToken(alice);686 const nestedToken = await collectionB.mintToken(alice, targetToken.nestingAccount());687688 await collectionB.addAdmin(alice, {Substrate: bob.address});689 await targetToken.transfer(alice, {Substrate: charlie.address});690691 const propertyKeys: string[] = [];692 let i = 0;693 for (const permission of permissions) {694 i++;695 if (!permission.permission.mutable) continue;696 697 let j = 0;698 for (const signer of permission.signers) {699 j++;700 const key = i + '_' + signer.address;701 propertyKeys.push(key);702703 await expect(704 nestedToken.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 705 `on setting permission #${i} by alice`,706 ).to.be.fulfilled;707708 await expect(709 nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), 710 `on adding property #${i} by signer #${j}`,711 ).to.be.fulfilled;712713 await expect(714 nestedToken.setProperties(signer, [{key, value: 'Serotonin stable'}]), 715 `on changing property #${i} by signer #${j}`,716 ).to.be.fulfilled;717 }718 }719720 const properties = await nestedToken.getProperties(propertyKeys);721 const tokenData = await nestedToken.getData();722 for (let i = 0; i < properties.length; i++) {723 expect(properties[i].value).to.be.equal('Serotonin stable');724 expect(tokenData!.properties[i].value).to.be.equal('Serotonin stable');725 }726 expect(await targetToken.getProperties()).to.be.empty;727 });728729 itSub('Deletes properties of a nested token according to permissions', async ({helper}) => {730 const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});731 const collectionB = await helper.nft.mintCollection(alice);732 const targetToken = await collectionA.mintToken(alice);733 const nestedToken = await collectionB.mintToken(alice, targetToken.nestingAccount());734735 await collectionB.addAdmin(alice, {Substrate: bob.address});736 await targetToken.transfer(alice, {Substrate: charlie.address});737738 const propertyKeys: string[] = [];739 let i = 0;740 for (const permission of permissions) {741 i++;742 if (!permission.permission.mutable) continue;743 744 let j = 0;745 for (const signer of permission.signers) {746 j++;747 const key = i + '_' + signer.address;748 propertyKeys.push(key);749750 await expect(751 nestedToken.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 752 `on setting permission #${i} by alice`,753 ).to.be.fulfilled;754755 await expect(756 nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), 757 `on adding property #${i} by signer #${j}`,758 ).to.be.fulfilled;759760 await expect(761 nestedToken.deleteProperties(signer, [key]), 762 `on deleting property #${i} by signer #${j}`,763 ).to.be.fulfilled;764 }765 }766767 expect(await nestedToken.getProperties(propertyKeys)).to.be.empty;768 expect((await nestedToken.getData())!.properties).to.be.empty;769 expect(await targetToken.getProperties()).to.be.empty;770 });771});772773describe('Negative Integration Test: Token Properties', () => {774 let alice: IKeyringPair; // collection owner775 let bob: IKeyringPair; // collection admin776 let charlie: IKeyringPair; // token owner777778 let constitution: {permission: any, signers: IKeyringPair[], sinner: IKeyringPair}[];779780 before(async () => {781 await usingPlaygrounds(async (helper, privateKey) => {782 const donor = privateKey('//Alice');783 let dave: IKeyringPair;784 [alice, bob, charlie, dave] = await helper.arrange.createAccounts([100n, 100n, 100n, 100n], donor);785786 // todo:playgrounds probably separate these tests later787 constitution = [788 {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},789 {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},790 {permission: {mutable: true, tokenOwner: true}, signers: [charlie], sinner: alice},791 {permission: {mutable: false, tokenOwner: true}, signers: [charlie], sinner: alice},792 {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie], sinner: dave},793 {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie], sinner: dave},794 ];795 });796 });797798 async function getConsumedSpace(api: any, collectionId: number, tokenId: number, mode: 'NFT' | 'RFT'): Promise<number> {799 return (await (mode == 'NFT' ? api.query.nonfungible : api.query.refungible).tokenProperties(collectionId, tokenId)).toJSON().consumedSpace;800 }801802 async function prepare(token: ITokenNonfungible | ITokenRefungible, pieces: bigint): Promise<number> {803 await token.collection.addAdmin(alice, {Substrate: bob.address});804 await token.transfer(alice, {Substrate: charlie.address}, pieces);805806 let i = 0;807 for (const passage of constitution) {808 i++;809 const signer = passage.signers[0];810 811 await expect(812 token.collection.setTokenPropertyPermissions(alice, [{key: `${i}`, permission: passage.permission}]), 813 `on setting permission ${i} by alice`,814 ).to.be.fulfilled;815816 await expect(817 token.setProperties(signer, [{key: `${i}`, value: 'Serotonin increase'}]), 818 `on adding property ${i} by ${signer.address}`,819 ).to.be.fulfilled;820 }821822 const originalSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 823 return originalSpace;824 }825826 async function testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token: ITokenNonfungible | ITokenRefungible, pieces: bigint) {827 const originalSpace = await prepare(token, pieces);828829 let i = 0;830 for (const forbiddance of constitution) {831 i++;832 if (!forbiddance.permission.mutable) continue;833834 await expect(835 token.setProperties(forbiddance.sinner, [{key: `${i}`, value: 'Serotonin down'}]), 836 `on failing to change property ${i} by the malefactor`,837 ).to.be.rejectedWith(/common\.NoPermission/);838839 await expect(840 token.deleteProperties(forbiddance.sinner, [`${i}`]), 841 `on failing to delete property ${i} by the malefactor`,842 ).to.be.rejectedWith(/common\.NoPermission/);843 }844845 const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 846 expect(consumedSpace).to.be.equal(originalSpace);847 }848849 itSub('Forbids changing/deleting properties of a token if the user is outside of permissions (NFT)', async ({helper}) => {850 const collection = await helper.nft.mintCollection(alice);851 const token = await collection.mintToken(alice);852 await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token, 1n);853 });854855 itSub.ifWithPallets('Forbids changing/deleting properties of a token if the user is outside of permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {856 const collection = await helper.rft.mintCollection(alice);857 const token = await collection.mintToken(alice, 100n);858 await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token, 100n);859 });860861 async function testForbidsChangingDeletingPropertiesIfPropertyImmutable(token: ITokenNonfungible | ITokenRefungible, pieces: bigint) {862 const originalSpace = await prepare(token, pieces);863864 let i = 0;865 for (const permission of constitution) {866 i++;867 if (permission.permission.mutable) continue;868869 await expect(870 token.setProperties(permission.signers[0], [{key: `${i}`, value: 'Serotonin down'}]), 871 `on failing to change property ${i} by signer #0`,872 ).to.be.rejectedWith(/common\.NoPermission/);873874 await expect(875 token.deleteProperties(permission.signers[0], [i.toString()]), 876 `on failing to delete property ${i} by signer #0`,877 ).to.be.rejectedWith(/common\.NoPermission/);878 }879 880 const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 881 expect(consumedSpace).to.be.equal(originalSpace);882 }883884 itSub('Forbids changing/deleting properties of a token if the property is permanent (immutable) (NFT)', async ({helper}) => {885 const collection = await helper.nft.mintCollection(alice);886 const token = await collection.mintToken(alice);887 await testForbidsChangingDeletingPropertiesIfPropertyImmutable(token, 1n);888 });889890 itSub.ifWithPallets('Forbids changing/deleting properties of a token if the property is permanent (immutable) (ReFungible)', [Pallets.ReFungible], async ({helper}) => {891 const collection = await helper.rft.mintCollection(alice);892 const token = await collection.mintToken(alice, 100n);893 await testForbidsChangingDeletingPropertiesIfPropertyImmutable(token, 100n);894 });895896 async function testForbidsAddingPropertiesIfPropertyNotDeclared(token: ITokenNonfungible | ITokenRefungible, pieces: bigint) {897 const originalSpace = await prepare(token, pieces);898899 await expect(900 token.setProperties(alice, [{key: 'non-existent', value: 'I exist!'}]), 901 'on failing to add a previously non-existent property',902 ).to.be.rejectedWith(/common\.NoPermission/);903 904 await expect(905 token.collection.setTokenPropertyPermissions(alice, [{key: 'now-existent', permission: {}}]), 906 'on setting a new non-permitted property',907 ).to.be.fulfilled;908909 await expect(910 token.setProperties(alice, [{key: 'now-existent', value: 'I exist!'}]), 911 'on failing to add a property forbidden by the \'None\' permission',912 ).to.be.rejectedWith(/common\.NoPermission/);913914 expect(await token.getProperties(['non-existent', 'now-existent'])).to.be.empty;915 916 const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 917 expect(consumedSpace).to.be.equal(originalSpace);918 }919920 itSub('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (NFT)', async ({helper}) => {921 const collection = await helper.nft.mintCollection(alice);922 const token = await collection.mintToken(alice);923 await testForbidsAddingPropertiesIfPropertyNotDeclared(token, 1n);924 });925926 itSub.ifWithPallets('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (ReFungible)', [Pallets.ReFungible], async ({helper}) => {927 const collection = await helper.rft.mintCollection(alice);928 const token = await collection.mintToken(alice, 100n);929 await testForbidsAddingPropertiesIfPropertyNotDeclared(token, 100n);930 });931932 async function testForbidsAddingTooManyProperties(token: ITokenNonfungible | ITokenRefungible, pieces: bigint) {933 const originalSpace = await prepare(token, pieces);934935 await expect(936 token.collection.setTokenPropertyPermissions(alice, [937 {key: 'a_holy_book', permission: {collectionAdmin: true, tokenOwner: true}}, 938 {key: 'young_years', permission: {collectionAdmin: true, tokenOwner: true}},939 ]), 940 'on setting new permissions for properties',941 ).to.be.fulfilled;942943 // Mute the general tx parsing error944 {945 console.error = () => {};946 await expect(token.setProperties(alice, [{key: 'a_holy_book', value: 'word '.repeat(6554)}]))947 .to.be.rejected;948 }949950 await expect(token.setProperties(alice, [951 {key: 'a_holy_book', value: 'word '.repeat(3277)}, 952 {key: 'young_years', value: 'neverending'.repeat(1490)},953 ])).to.be.rejectedWith(/common\.NoSpaceForProperty/);954 955 expect(await token.getProperties(['a_holy_book', 'young_years'])).to.be.empty;956 const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 957 expect(consumedSpace).to.be.equal(originalSpace);958 }959960 itSub('Forbids adding too many properties to a token (NFT)', async ({helper}) => {961 const collection = await helper.nft.mintCollection(alice);962 const token = await collection.mintToken(alice);963 await testForbidsAddingTooManyProperties(token, 1n);964 });965966 itSub.ifWithPallets('Forbids adding too many properties to a token (ReFungible)', [Pallets.ReFungible], async ({helper}) => {967 const collection = await helper.rft.mintCollection(alice);968 const token = await collection.mintToken(alice, 100n);969 await testForbidsAddingTooManyProperties(token, 100n);970 });971});972973describe('ReFungible token properties permissions tests', () => {974 let alice: IKeyringPair;975 let bob: IKeyringPair;976 let charlie: IKeyringPair;977978 before(async function() {979 await usingPlaygrounds(async (helper, privateKey) => {980 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);981982 const donor = privateKey('//Alice');983 [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);984 });985 });986987 async function prepare(helper: UniqueHelper): Promise<ITokenRefungible> {988 const collection = await helper.rft.mintCollection(alice);989 const token = await collection.mintToken(alice, 100n);990 991 await collection.addAdmin(alice, {Substrate: bob.address});992 await collection.setTokenPropertyPermissions(alice, [{key: 'fractals', permission: {mutable: true, tokenOwner: true}}]);993 994 return token;995 }996997 itSub('Forbids adding token property with tokenOwner==true when signer doesn\'t have all pieces', async ({helper}) => {998 const token = await prepare(helper);9991000 await token.transfer(alice, {Substrate: charlie.address}, 33n);10011002 await expect(token.setProperties(alice, [1003 {key: 'fractals', value: 'multiverse'}, 1004 ])).to.be.rejectedWith(/common\.NoPermission/);1005 });10061007 itSub('Forbids mutating token property with tokenOwher==true when signer doesn\'t have all pieces', async ({helper}) => {1008 const token = await prepare(helper);10091010 await expect(token.collection.setTokenPropertyPermissions(alice, [{key: 'fractals', permission: {mutable:true, tokenOwner: true}}]))1011 .to.be.fulfilled;10121013 await expect(token.setProperties(alice, [1014 {key: 'fractals', value: 'multiverse'}, 1015 ])).to.be.fulfilled;10161017 await token.transfer(alice, {Substrate: charlie.address}, 33n);10181019 await expect(token.setProperties(alice, [1020 {key: 'fractals', value: 'want to rule the world'}, 1021 ])).to.be.rejectedWith(/common\.NoPermission/);1022 });10231024 itSub('Forbids deleting token property with tokenOwner==true when signer doesn\'t have all pieces', async ({helper}) => {1025 const token = await prepare(helper);10261027 await expect(token.setProperties(alice, [1028 {key: 'fractals', value: 'one headline - why believe it'}, 1029 ])).to.be.fulfilled;10301031 await token.transfer(alice, {Substrate: charlie.address}, 33n);10321033 await expect(token.deleteProperties(alice, ['fractals'])).1034 to.be.rejectedWith(/common\.NoPermission/);1035 });10361037 itSub('Allows token property mutation with collectionOwner==true when admin doesn\'t have all pieces', async ({helper}) => {1038 const token = await prepare(helper);10391040 await token.transfer(alice, {Substrate: charlie.address}, 33n);10411042 await expect(token.collection.setTokenPropertyPermissions(alice, [{key: 'fractals', permission: {mutable:true, collectionAdmin: true}}]))1043 .to.be.fulfilled;10441045 await expect(token.setProperties(alice, [1046 {key: 'fractals', value: 'multiverse'}, 1047 ])).to.be.fulfilled;1048 });1049});1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '../util/playgrounds';19import {UniqueHelper, UniqueBaseCollection, UniqueNFTCollection, UniqueNFToken, UniqueRFTCollection, UniqueRFToken} from '../util/playgrounds/unique';2021// ---------- COLLECTION PROPERTIES2223describe('Integration Test: Collection Properties', () => {24 let alice: IKeyringPair;25 let bob: IKeyringPair;2627 before(async () => {28 await usingPlaygrounds(async (helper, privateKey) => {29 const donor = privateKey('//Alice');30 [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);31 });32 });3334 itSub('Properties are initially empty', async ({helper}) => {35 const collection = await helper.nft.mintCollection(alice);36 expect(await collection.getProperties()).to.be.empty;37 });3839 async function testSetsPropertiesForCollection(collection: UniqueBaseCollection) {40 // As owner41 await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}])).to.be.fulfilled;4243 await collection.addAdmin(alice, {Substrate: bob.address});4445 // As administrator46 await expect(collection.setProperties(bob, [{key: 'black_hole'}])).to.be.fulfilled;4748 const properties = await collection.getProperties();49 expect(properties).to.include.deep.members([50 {key: 'electron', value: 'come bond'},51 {key: 'black_hole', value: ''},52 ]);53 }5455 itSub('Sets properties for a NFT collection', async ({helper}) => {56 await testSetsPropertiesForCollection(await helper.nft.mintCollection(alice));57 });5859 itSub.ifWithPallets('Sets properties for a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {60 await testSetsPropertiesForCollection(await helper.rft.mintCollection(alice));61 });6263 async function testCheckValidNames(collection: UniqueBaseCollection) {64 // alpha symbols65 await expect(collection.setProperties(alice, [{key: 'answer'}])).to.be.fulfilled;6667 // numeric symbols68 await expect(collection.setProperties(alice, [{key: '451'}])).to.be.fulfilled;6970 // underscore symbol71 await expect(collection.setProperties(alice, [{key: 'black_hole'}])).to.be.fulfilled;7273 // dash symbol74 await expect(collection.setProperties(alice, [{key: '-'}])).to.be.fulfilled;7576 // dot symbol77 await expect(collection.setProperties(alice, [{key: 'once.in.a.long.long.while...', value: 'you get a little lost'}])).to.be.fulfilled;7879 const properties = await collection.getProperties();80 expect(properties).to.include.deep.members([81 {key: 'answer', value: ''},82 {key: '451', value: ''},83 {key: 'black_hole', value: ''},84 {key: '-', value: ''},85 {key: 'once.in.a.long.long.while...', value: 'you get a little lost'},86 ]);87 }8889 itSub('Check valid names for NFT collection properties keys', async ({helper}) => {90 await testCheckValidNames(await helper.nft.mintCollection(alice));91 });9293 itSub.ifWithPallets('Check valid names for ReFungible collection properties keys', [Pallets.ReFungible], async ({helper}) => {94 await testCheckValidNames(await helper.rft.mintCollection(alice));95 });9697 async function testChangesProperties(collection: UniqueBaseCollection) {98 await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: ''}])).to.be.fulfilled;99100 // Mutate the properties101 await expect(collection.setProperties(alice, [{key: 'black_hole', value: 'LIGO'}])).to.be.fulfilled;102103 const properties = await collection.getProperties();104 expect(properties).to.include.deep.members([105 {key: 'electron', value: 'come bond'},106 {key: 'black_hole', value: 'LIGO'},107 ]);108 }109110 itSub('Changes properties of a NFT collection', async ({helper}) => {111 await testChangesProperties(await helper.nft.mintCollection(alice));112 });113114 itSub.ifWithPallets('Changes properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {115 await testChangesProperties(await helper.rft.mintCollection(alice));116 });117118 async function testDeleteProperties(collection: UniqueBaseCollection) {119 await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}])).to.be.fulfilled;120121 await expect(collection.deleteProperties(alice, ['electron'])).to.be.fulfilled;122123 const properties = await collection.getProperties(['black_hole', 'electron']);124 expect(properties).to.be.deep.equal([125 {key: 'black_hole', value: 'LIGO'},126 ]);127 }128129 itSub('Deletes properties of a NFT collection', async ({helper}) => {130 await testDeleteProperties(await helper.nft.mintCollection(alice));131 });132133 itSub.ifWithPallets('Deletes properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {134 await testDeleteProperties(await helper.rft.mintCollection(alice));135 });136});137138describe('Negative Integration Test: Collection Properties', () => {139 let alice: IKeyringPair;140 let bob: IKeyringPair;141142 before(async () => {143 await usingPlaygrounds(async (helper, privateKey) => {144 const donor = privateKey('//Alice');145 [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor);146 });147 });148 149 async function testFailsSetPropertiesIfNotOwnerOrAdmin(collection: UniqueBaseCollection) { 150 await expect(collection.setProperties(bob, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]))151 .to.be.rejectedWith(/common\.NoPermission/);152153 expect(await collection.getProperties()).to.be.empty;154 }155156 itSub('Fails to set properties in a NFT collection if not its onwer/administrator', async ({helper}) => {157 await testFailsSetPropertiesIfNotOwnerOrAdmin(await helper.nft.mintCollection(alice));158 });159160 itSub.ifWithPallets('Fails to set properties in a ReFungible collection if not its onwer/administrator', [Pallets.ReFungible], async ({helper}) => {161 await testFailsSetPropertiesIfNotOwnerOrAdmin(await helper.rft.mintCollection(alice));162 });163 164 async function testFailsSetPropertiesThatExeedLimits(collection: UniqueBaseCollection) {165 const spaceLimit = (await (collection.helper!.api! as any).query.common.collectionProperties(collection.collectionId)).spaceLimit.toNumber();166 167 // Mute the general tx parsing error, too many bytes to process168 {169 console.error = () => {};170 await expect(collection.setProperties(alice, [171 {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 9))},172 ])).to.be.rejected;173 }174175 expect(await collection.getProperties(['electron'])).to.be.empty;176177 await expect(collection.setProperties(alice, [178 {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 18))}, 179 {key: 'black_hole', value: '0'.repeat(Math.ceil(spaceLimit! / 2))}, 180 ])).to.be.rejectedWith(/common\.NoSpaceForProperty/);181182 expect(await collection.getProperties(['electron', 'black_hole'])).to.be.empty;183 }184185 itSub('Fails to set properties that exceed the limits (NFT)', async ({helper}) => {186 await testFailsSetPropertiesThatExeedLimits(await helper.nft.mintCollection(alice));187 });188189 itSub.ifWithPallets('Fails to set properties that exceed the limits (ReFungible)', [Pallets.ReFungible], async ({helper}) => {190 await testFailsSetPropertiesThatExeedLimits(await helper.rft.mintCollection(alice));191 });192 193 async function testFailsSetMorePropertiesThanAllowed(collection: UniqueBaseCollection) {194 const propertiesToBeSet = [];195 for (let i = 0; i < 65; i++) {196 propertiesToBeSet.push({197 key: 'electron_' + i,198 value: Math.random() > 0.5 ? 'high' : 'low',199 });200 }201202 await expect(collection.setProperties(alice, propertiesToBeSet)).203 to.be.rejectedWith(/common\.PropertyLimitReached/);204205 expect(await collection.getProperties()).to.be.empty;206 }207208 itSub('Fails to set more properties than it is allowed (NFT)', async ({helper}) => {209 await testFailsSetMorePropertiesThanAllowed(await helper.nft.mintCollection(alice));210 });211212 itSub.ifWithPallets('Fails to set more properties than it is allowed (ReFungible)', [Pallets.ReFungible], async ({helper}) => {213 await testFailsSetMorePropertiesThanAllowed(await helper.rft.mintCollection(alice));214 });215 216 async function testFailsSetPropertiesWithInvalidNames(collection: UniqueBaseCollection) {217 const invalidProperties = [218 [{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}],219 [{key: 'Mr/Sandman', value: 'Bring me a gene'}],220 [{key: 'déjà vu', value: 'hmm...'}],221 ];222223 for (let i = 0; i < invalidProperties.length; i++) {224 await expect(225 collection.setProperties(alice, invalidProperties[i]), 226 `on rejecting the new badly-named property #${i}`,227 ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);228 }229230 await expect(231 collection.setProperties(alice, [{key: '', value: 'nothing must not exist'}]), 232 'on rejecting an unnamed property',233 ).to.be.rejectedWith(/common\.EmptyPropertyKey/);234235 await expect(236 collection.setProperties(alice, [{key: 'CRISPR-Cas9', value: 'rewriting nature!'}]), 237 'on setting the correctly-but-still-badly-named property',238 ).to.be.fulfilled;239240 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat('CRISPR-Cas9').concat('');241242 const properties = await collection.getProperties(keys);243 expect(properties).to.be.deep.equal([244 {key: 'CRISPR-Cas9', value: 'rewriting nature!'},245 ]);246247 for (let i = 0; i < invalidProperties.length; i++) {248 await expect(249 collection.deleteProperties(alice, invalidProperties[i].map(propertySet => propertySet.key)), 250 `on trying to delete the non-existent badly-named property #${i}`,251 ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);252 }253 }254255 itSub('Fails to set properties with invalid names (NFT)', async ({helper}) => {256 await testFailsSetPropertiesWithInvalidNames(await helper.nft.mintCollection(alice));257 });258259 itSub.ifWithPallets('Fails to set properties with invalid names (ReFungible)', [Pallets.ReFungible], async ({helper}) => {260 await testFailsSetPropertiesWithInvalidNames(await helper.rft.mintCollection(alice));261 });262});263264// ---------- ACCESS RIGHTS265266describe('Integration Test: Access Rights to Token Properties', () => {267 let alice: IKeyringPair;268 let bob: IKeyringPair;269270 before(async () => {271 await usingPlaygrounds(async (helper, privateKey) => {272 const donor = privateKey('//Alice');273 [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor);274 });275 });276 277 itSub('Reads access rights to properties of a collection', async ({helper}) => {278 const collection = await helper.nft.mintCollection(alice);279 const propertyRights = (await helper.api!.query.common.collectionPropertyPermissions(collection.collectionId)).toJSON();280 expect(propertyRights).to.be.empty;281 });282 283 async function testSetsAccessRightsToProperties(collection: UniqueNFTCollection | UniqueRFTCollection) { 284 await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: true}}]))285 .to.be.fulfilled;286287 await collection.addAdmin(alice, {Substrate: bob.address});288289 await expect(collection.setTokenPropertyPermissions(bob, [{key: 'mindgame', permission: {collectionAdmin: true, tokenOwner: false}}]))290 .to.be.fulfilled;291292 const propertyRights = await collection.getPropertyPermissions(['skullduggery', 'mindgame']);293 expect(propertyRights).to.include.deep.members([294 {key: 'skullduggery', permission: {mutable: true, collectionAdmin: false, tokenOwner: false}},295 {key: 'mindgame', permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},296 ]);297 }298299 itSub('Sets access rights to properties of a collection (NFT)', async ({helper}) => {300 await testSetsAccessRightsToProperties(await helper.nft.mintCollection(alice));301 });302303 itSub.ifWithPallets('Sets access rights to properties of a collection (ReFungible)', [Pallets.ReFungible], async ({helper}) => {304 await testSetsAccessRightsToProperties(await helper.rft.mintCollection(alice));305 });306 307 async function testChangesAccessRightsToProperty(collection: UniqueNFTCollection | UniqueRFTCollection) {308 await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: true, collectionAdmin: true}}]))309 .to.be.fulfilled;310311 await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]))312 .to.be.fulfilled;313314 const propertyRights = await collection.getPropertyPermissions();315 expect(propertyRights).to.be.deep.equal([316 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},317 ]);318 }319320 itSub('Changes access rights to properties of a NFT collection', async ({helper}) => {321 await testChangesAccessRightsToProperty(await helper.nft.mintCollection(alice));322 });323324 itSub.ifWithPallets('Changes access rights to properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {325 await testChangesAccessRightsToProperty(await helper.rft.mintCollection(alice));326 });327});328329describe('Negative Integration Test: Access Rights to Token Properties', () => {330 let alice: IKeyringPair;331 let bob: IKeyringPair;332333 before(async () => {334 await usingPlaygrounds(async (helper, privateKey) => {335 const donor = privateKey('//Alice');336 [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);337 });338 });339340 async function testPreventsFromSettingAccessRightsNotAdminOrOwner(collection: UniqueNFTCollection | UniqueRFTCollection) {341 await expect(collection.setTokenPropertyPermissions(bob, [{key: 'skullduggery', permission: {mutable: true, tokenOwner: true}}]))342 .to.be.rejectedWith(/common\.NoPermission/);343344 const propertyRights = await collection.getPropertyPermissions(['skullduggery']);345 expect(propertyRights).to.be.empty;346 }347348 itSub('Prevents from setting access rights to properties of a NFT collection if not an onwer/admin', async ({helper}) => {349 await testPreventsFromSettingAccessRightsNotAdminOrOwner(await helper.nft.mintCollection(alice));350 });351352 itSub.ifWithPallets('Prevents from setting access rights to properties of a ReFungible collection if not an onwer/admin', [Pallets.ReFungible], async ({helper}) => {353 await testPreventsFromSettingAccessRightsNotAdminOrOwner(await helper.rft.mintCollection(alice));354 });355356 async function testPreventFromAddingTooManyPossibleProperties(collection: UniqueNFTCollection | UniqueRFTCollection) { 357 const constitution = [];358 for (let i = 0; i < 65; i++) {359 constitution.push({360 key: 'property_' + i,361 permission: Math.random() > 0.5 ? {mutable: true, collectionAdmin: true, tokenOwner: true} : {},362 });363 }364365 await expect(collection.setTokenPropertyPermissions(alice, constitution))366 .to.be.rejectedWith(/common\.PropertyLimitReached/);367368 const propertyRights = await collection.getPropertyPermissions();369 expect(propertyRights).to.be.empty;370 }371372 itSub('Prevents from adding too many possible properties (NFT)', async ({helper}) => {373 await testPreventFromAddingTooManyPossibleProperties(await helper.nft.mintCollection(alice));374 });375376 itSub.ifWithPallets('Prevents from adding too many possible properties (ReFungible)', [Pallets.ReFungible], async ({helper}) => {377 await testPreventFromAddingTooManyPossibleProperties(await helper.rft.mintCollection(alice));378 });379380 async function testPreventAccessRightsModifiedIfConstant(collection: UniqueNFTCollection | UniqueRFTCollection) {381 await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]))382 .to.be.fulfilled;383384 await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {collectionAdmin: true}}]))385 .to.be.rejectedWith(/common\.NoPermission/);386387 const propertyRights = await collection.getPropertyPermissions(['skullduggery']);388 expect(propertyRights).to.deep.equal([389 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},390 ]);391 }392393 itSub('Prevents access rights to be modified if constant (NFT)', async ({helper}) => {394 await testPreventAccessRightsModifiedIfConstant(await helper.nft.mintCollection(alice));395 });396397 itSub.ifWithPallets('Prevents access rights to be modified if constant (ReFungible)', [Pallets.ReFungible], async ({helper}) => {398 await testPreventAccessRightsModifiedIfConstant(await helper.rft.mintCollection(alice));399 });400401 async function testPreventsAddingPropertiesWithInvalidNames(collection: UniqueNFTCollection | UniqueRFTCollection) {402 const invalidProperties = [403 [{key: 'skullduggery', permission: {tokenOwner: true}}, {key: 'im possible', permission: {collectionAdmin: true}}],404 [{key: 'G#4', permission: {tokenOwner: true}}],405 [{key: 'HÆMILTON', permission: {mutable: false, collectionAdmin: true, tokenOwner: true}}],406 ];407408 for (let i = 0; i < invalidProperties.length; i++) {409 await expect(410 collection.setTokenPropertyPermissions(alice, invalidProperties[i]), 411 `on setting the new badly-named property #${i}`,412 ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);413 }414415 await expect(416 collection.setTokenPropertyPermissions(alice, [{key: '', permission: {}}]), 417 'on rejecting an unnamed property',418 ).to.be.rejectedWith(/common\.EmptyPropertyKey/);419420 const correctKey = '--0x03116e387820CA05'; // PolkadotJS would parse this as an already encoded hex-string421 await expect(422 collection.setTokenPropertyPermissions(alice, [423 {key: correctKey, permission: {collectionAdmin: true}},424 ]), 425 'on setting the correctly-but-still-badly-named property',426 ).to.be.fulfilled;427428 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat(correctKey).concat('');429430 const propertyRights = await collection.getPropertyPermissions(keys);431 expect(propertyRights).to.be.deep.equal([432 {key: correctKey, permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},433 ]);434 }435436 itSub('Prevents adding properties with invalid names (NFT)', async ({helper}) => {437 await testPreventsAddingPropertiesWithInvalidNames(await helper.nft.mintCollection(alice));438 });439440 itSub.ifWithPallets('Prevents adding properties with invalid names (ReFungible)', [Pallets.ReFungible], async ({helper}) => {441 await testPreventsAddingPropertiesWithInvalidNames(await helper.rft.mintCollection(alice));442 });443});444445// ---------- TOKEN PROPERTIES446447describe('Integration Test: Token Properties', () => {448 let alice: IKeyringPair; // collection owner449 let bob: IKeyringPair; // collection admin450 let charlie: IKeyringPair; // token owner451452 let permissions: {permission: any, signers: IKeyringPair[]}[];453454 before(async () => {455 await usingPlaygrounds(async (helper, privateKey) => {456 const donor = privateKey('//Alice');457 [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);458 });459460 // todo:playgrounds probably separate these tests later461 permissions = [462 {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob]},463 {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob]},464 {permission: {mutable: true, tokenOwner: true}, signers: [charlie]},465 {permission: {mutable: false, tokenOwner: true}, signers: [charlie]},466 {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},467 {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},468 ];469 });470 471 async function testReadsYetEmptyProperties(token: UniqueNFToken | UniqueRFToken) {472 const properties = await token.getProperties();473 expect(properties).to.be.empty;474475 const tokenData = await token.getData();476 expect(tokenData!.properties).to.be.empty;477 }478479 itSub('Reads yet empty properties of a token (NFT)', async ({helper}) => {480 const collection = await helper.nft.mintCollection(alice);481 const token = await collection.mintToken(alice);482 await testReadsYetEmptyProperties(token);483 });484485 itSub.ifWithPallets('Reads yet empty properties of a token (ReFungible)', [Pallets.ReFungible], async ({helper}) => {486 const collection = await helper.rft.mintCollection(alice);487 const token = await collection.mintToken(alice);488 await testReadsYetEmptyProperties(token);489 });490491 async function testAssignPropertiesAccordingToPermissions(token: UniqueNFToken | UniqueRFToken, pieces: bigint) {492 await token.collection.addAdmin(alice, {Substrate: bob.address});493 await token.transfer(alice, {Substrate: charlie.address}, pieces);494495 const propertyKeys: string[] = [];496 let i = 0;497 for (const permission of permissions) {498 i++;499 let j = 0;500 for (const signer of permission.signers) {501 j++;502 const key = i + '_' + signer.address;503 propertyKeys.push(key);504505 await expect(506 token.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 507 `on setting permission #${i} by alice`,508 ).to.be.fulfilled;509510 await expect(511 token.setProperties(signer, [{key: key, value: 'Serotonin increase'}]), 512 `on adding property #${i} by signer #${j}`,513 ).to.be.fulfilled;514 }515 }516517 const properties = await token.getProperties(propertyKeys);518 const tokenData = await token.getData();519 for (let i = 0; i < properties.length; i++) {520 expect(properties[i].value).to.be.equal('Serotonin increase');521 expect(tokenData!.properties[i].value).to.be.equal('Serotonin increase');522 }523 }524525 itSub('Assigns properties to a token according to permissions (NFT)', async ({helper}) => {526 const collection = await helper.nft.mintCollection(alice);527 const token = await collection.mintToken(alice);528 await testAssignPropertiesAccordingToPermissions(token, 1n);529 });530531 itSub.ifWithPallets('Assigns properties to a token according to permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {532 const collection = await helper.rft.mintCollection(alice);533 const token = await collection.mintToken(alice, 100n);534 await testAssignPropertiesAccordingToPermissions(token, 100n);535 });536537 async function testChangesPropertiesAccordingPermission(token: UniqueNFToken | UniqueRFToken, pieces: bigint) {538 await token.collection.addAdmin(alice, {Substrate: bob.address});539 await token.transfer(alice, {Substrate: charlie.address}, pieces);540541 const propertyKeys: string[] = [];542 let i = 0;543 for (const permission of permissions) {544 i++;545 if (!permission.permission.mutable) continue;546 547 let j = 0;548 for (const signer of permission.signers) {549 j++;550 const key = i + '_' + signer.address;551 propertyKeys.push(key);552553 await expect(554 token.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 555 `on setting permission #${i} by alice`,556 ).to.be.fulfilled;557558 await expect(559 token.setProperties(signer, [{key, value: 'Serotonin increase'}]), 560 `on adding property #${i} by signer #${j}`,561 ).to.be.fulfilled;562563 await expect(564 token.setProperties(signer, [{key, value: 'Serotonin stable'}]), 565 `on changing property #${i} by signer #${j}`,566 ).to.be.fulfilled;567 }568 }569570 const properties = await token.getProperties(propertyKeys);571 const tokenData = await token.getData();572 for (let i = 0; i < properties.length; i++) {573 expect(properties[i].value).to.be.equal('Serotonin stable');574 expect(tokenData!.properties[i].value).to.be.equal('Serotonin stable');575 }576 }577578 itSub('Changes properties of a token according to permissions (NFT)', async ({helper}) => {579 const collection = await helper.nft.mintCollection(alice);580 const token = await collection.mintToken(alice);581 await testChangesPropertiesAccordingPermission(token, 1n);582 });583584 itSub.ifWithPallets('Changes properties of a token according to permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {585 const collection = await helper.rft.mintCollection(alice);586 const token = await collection.mintToken(alice, 100n);587 await testChangesPropertiesAccordingPermission(token, 100n);588 });589590 async function testDeletePropertiesAccordingPermission(token: UniqueNFToken | UniqueRFToken, pieces: bigint) {591 await token.collection.addAdmin(alice, {Substrate: bob.address});592 await token.transfer(alice, {Substrate: charlie.address}, pieces);593594 const propertyKeys: string[] = [];595 let i = 0;596597 for (const permission of permissions) {598 i++;599 if (!permission.permission.mutable) continue;600 601 let j = 0;602 for (const signer of permission.signers) {603 j++;604 const key = i + '_' + signer.address;605 propertyKeys.push(key);606607 await expect(608 token.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 609 `on setting permission #${i} by alice`,610 ).to.be.fulfilled;611612 await expect(613 token.setProperties(signer, [{key, value: 'Serotonin increase'}]), 614 `on adding property #${i} by signer #${j}`,615 ).to.be.fulfilled;616617 await expect(618 token.deleteProperties(signer, [key]), 619 `on deleting property #${i} by signer #${j}`,620 ).to.be.fulfilled;621 }622 }623624 expect(await token.getProperties(propertyKeys)).to.be.empty;625 expect((await token.getData())!.properties).to.be.empty;626 }627 628 itSub('Deletes properties of a token according to permissions (NFT)', async ({helper}) => {629 const collection = await helper.nft.mintCollection(alice);630 const token = await collection.mintToken(alice);631 await testDeletePropertiesAccordingPermission(token, 1n);632 });633634 itSub.ifWithPallets('Deletes properties of a token according to permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {635 const collection = await helper.rft.mintCollection(alice);636 const token = await collection.mintToken(alice, 100n);637 await testDeletePropertiesAccordingPermission(token, 100n);638 });639640 itSub('Assigns properties to a nested token according to permissions', async ({helper}) => {641 const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});642 const collectionB = await helper.nft.mintCollection(alice);643 const targetToken = await collectionA.mintToken(alice);644 const nestedToken = await collectionB.mintToken(alice, targetToken.nestingAccount());645646 await collectionB.addAdmin(alice, {Substrate: bob.address});647 await targetToken.transfer(alice, {Substrate: charlie.address});648649 const propertyKeys: string[] = [];650 let i = 0;651 for (const permission of permissions) {652 i++;653 let j = 0;654 for (const signer of permission.signers) {655 j++;656 const key = i + '_' + signer.address;657 propertyKeys.push(key);658 659 await expect(660 nestedToken.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 661 `on setting permission #${i} by alice`,662 ).to.be.fulfilled;663664 await expect(665 nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), 666 `on adding property #${i} by signer #${j}`,667 ).to.be.fulfilled;668 }669670 }671672 const properties = await nestedToken.getProperties(propertyKeys);673 const tokenData = await nestedToken.getData();674 for (let i = 0; i < properties.length; i++) {675 expect(properties[i].value).to.be.equal('Serotonin increase');676 expect(tokenData!.properties[i].value).to.be.equal('Serotonin increase');677 }678 expect(await targetToken.getProperties()).to.be.empty;679 });680681 itSub('Changes properties of a nested token according to permissions', async ({helper}) => {682 const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});683 const collectionB = await helper.nft.mintCollection(alice);684 const targetToken = await collectionA.mintToken(alice);685 const nestedToken = await collectionB.mintToken(alice, targetToken.nestingAccount());686687 await collectionB.addAdmin(alice, {Substrate: bob.address});688 await targetToken.transfer(alice, {Substrate: charlie.address});689690 const propertyKeys: string[] = [];691 let i = 0;692 for (const permission of permissions) {693 i++;694 if (!permission.permission.mutable) continue;695 696 let j = 0;697 for (const signer of permission.signers) {698 j++;699 const key = i + '_' + signer.address;700 propertyKeys.push(key);701702 await expect(703 nestedToken.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 704 `on setting permission #${i} by alice`,705 ).to.be.fulfilled;706707 await expect(708 nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), 709 `on adding property #${i} by signer #${j}`,710 ).to.be.fulfilled;711712 await expect(713 nestedToken.setProperties(signer, [{key, value: 'Serotonin stable'}]), 714 `on changing property #${i} by signer #${j}`,715 ).to.be.fulfilled;716 }717 }718719 const properties = await nestedToken.getProperties(propertyKeys);720 const tokenData = await nestedToken.getData();721 for (let i = 0; i < properties.length; i++) {722 expect(properties[i].value).to.be.equal('Serotonin stable');723 expect(tokenData!.properties[i].value).to.be.equal('Serotonin stable');724 }725 expect(await targetToken.getProperties()).to.be.empty;726 });727728 itSub('Deletes properties of a nested token according to permissions', async ({helper}) => {729 const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});730 const collectionB = await helper.nft.mintCollection(alice);731 const targetToken = await collectionA.mintToken(alice);732 const nestedToken = await collectionB.mintToken(alice, targetToken.nestingAccount());733734 await collectionB.addAdmin(alice, {Substrate: bob.address});735 await targetToken.transfer(alice, {Substrate: charlie.address});736737 const propertyKeys: string[] = [];738 let i = 0;739 for (const permission of permissions) {740 i++;741 if (!permission.permission.mutable) continue;742 743 let j = 0;744 for (const signer of permission.signers) {745 j++;746 const key = i + '_' + signer.address;747 propertyKeys.push(key);748749 await expect(750 nestedToken.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 751 `on setting permission #${i} by alice`,752 ).to.be.fulfilled;753754 await expect(755 nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), 756 `on adding property #${i} by signer #${j}`,757 ).to.be.fulfilled;758759 await expect(760 nestedToken.deleteProperties(signer, [key]), 761 `on deleting property #${i} by signer #${j}`,762 ).to.be.fulfilled;763 }764 }765766 expect(await nestedToken.getProperties(propertyKeys)).to.be.empty;767 expect((await nestedToken.getData())!.properties).to.be.empty;768 expect(await targetToken.getProperties()).to.be.empty;769 });770});771772describe('Negative Integration Test: Token Properties', () => {773 let alice: IKeyringPair; // collection owner774 let bob: IKeyringPair; // collection admin775 let charlie: IKeyringPair; // token owner776777 let constitution: {permission: any, signers: IKeyringPair[], sinner: IKeyringPair}[];778779 before(async () => {780 await usingPlaygrounds(async (helper, privateKey) => {781 const donor = privateKey('//Alice');782 let dave: IKeyringPair;783 [alice, bob, charlie, dave] = await helper.arrange.createAccounts([100n, 100n, 100n, 100n], donor);784785 // todo:playgrounds probably separate these tests later786 constitution = [787 {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},788 {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},789 {permission: {mutable: true, tokenOwner: true}, signers: [charlie], sinner: alice},790 {permission: {mutable: false, tokenOwner: true}, signers: [charlie], sinner: alice},791 {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie], sinner: dave},792 {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie], sinner: dave},793 ];794 });795 });796797 async function getConsumedSpace(api: any, collectionId: number, tokenId: number, mode: 'NFT' | 'RFT'): Promise<number> {798 return (await (mode == 'NFT' ? api.query.nonfungible : api.query.refungible).tokenProperties(collectionId, tokenId)).toJSON().consumedSpace;799 }800801 async function prepare(token: UniqueNFToken | UniqueRFToken, pieces: bigint): Promise<number> {802 await token.collection.addAdmin(alice, {Substrate: bob.address});803 await token.transfer(alice, {Substrate: charlie.address}, pieces);804805 let i = 0;806 for (const passage of constitution) {807 i++;808 const signer = passage.signers[0];809 810 await expect(811 token.collection.setTokenPropertyPermissions(alice, [{key: `${i}`, permission: passage.permission}]), 812 `on setting permission ${i} by alice`,813 ).to.be.fulfilled;814815 await expect(816 token.setProperties(signer, [{key: `${i}`, value: 'Serotonin increase'}]), 817 `on adding property ${i} by ${signer.address}`,818 ).to.be.fulfilled;819 }820821 const originalSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 822 return originalSpace;823 }824825 async function testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token: UniqueNFToken | UniqueRFToken, pieces: bigint) {826 const originalSpace = await prepare(token, pieces);827828 let i = 0;829 for (const forbiddance of constitution) {830 i++;831 if (!forbiddance.permission.mutable) continue;832833 await expect(834 token.setProperties(forbiddance.sinner, [{key: `${i}`, value: 'Serotonin down'}]), 835 `on failing to change property ${i} by the malefactor`,836 ).to.be.rejectedWith(/common\.NoPermission/);837838 await expect(839 token.deleteProperties(forbiddance.sinner, [`${i}`]), 840 `on failing to delete property ${i} by the malefactor`,841 ).to.be.rejectedWith(/common\.NoPermission/);842 }843844 const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 845 expect(consumedSpace).to.be.equal(originalSpace);846 }847848 itSub('Forbids changing/deleting properties of a token if the user is outside of permissions (NFT)', async ({helper}) => {849 const collection = await helper.nft.mintCollection(alice);850 const token = await collection.mintToken(alice);851 await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token, 1n);852 });853854 itSub.ifWithPallets('Forbids changing/deleting properties of a token if the user is outside of permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {855 const collection = await helper.rft.mintCollection(alice);856 const token = await collection.mintToken(alice, 100n);857 await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token, 100n);858 });859860 async function testForbidsChangingDeletingPropertiesIfPropertyImmutable(token: UniqueNFToken | UniqueRFToken, pieces: bigint) {861 const originalSpace = await prepare(token, pieces);862863 let i = 0;864 for (const permission of constitution) {865 i++;866 if (permission.permission.mutable) continue;867868 await expect(869 token.setProperties(permission.signers[0], [{key: `${i}`, value: 'Serotonin down'}]), 870 `on failing to change property ${i} by signer #0`,871 ).to.be.rejectedWith(/common\.NoPermission/);872873 await expect(874 token.deleteProperties(permission.signers[0], [i.toString()]), 875 `on failing to delete property ${i} by signer #0`,876 ).to.be.rejectedWith(/common\.NoPermission/);877 }878 879 const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 880 expect(consumedSpace).to.be.equal(originalSpace);881 }882883 itSub('Forbids changing/deleting properties of a token if the property is permanent (immutable) (NFT)', async ({helper}) => {884 const collection = await helper.nft.mintCollection(alice);885 const token = await collection.mintToken(alice);886 await testForbidsChangingDeletingPropertiesIfPropertyImmutable(token, 1n);887 });888889 itSub.ifWithPallets('Forbids changing/deleting properties of a token if the property is permanent (immutable) (ReFungible)', [Pallets.ReFungible], async ({helper}) => {890 const collection = await helper.rft.mintCollection(alice);891 const token = await collection.mintToken(alice, 100n);892 await testForbidsChangingDeletingPropertiesIfPropertyImmutable(token, 100n);893 });894895 async function testForbidsAddingPropertiesIfPropertyNotDeclared(token: UniqueNFToken | UniqueRFToken, pieces: bigint) {896 const originalSpace = await prepare(token, pieces);897898 await expect(899 token.setProperties(alice, [{key: 'non-existent', value: 'I exist!'}]), 900 'on failing to add a previously non-existent property',901 ).to.be.rejectedWith(/common\.NoPermission/);902 903 await expect(904 token.collection.setTokenPropertyPermissions(alice, [{key: 'now-existent', permission: {}}]), 905 'on setting a new non-permitted property',906 ).to.be.fulfilled;907908 await expect(909 token.setProperties(alice, [{key: 'now-existent', value: 'I exist!'}]), 910 'on failing to add a property forbidden by the \'None\' permission',911 ).to.be.rejectedWith(/common\.NoPermission/);912913 expect(await token.getProperties(['non-existent', 'now-existent'])).to.be.empty;914 915 const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 916 expect(consumedSpace).to.be.equal(originalSpace);917 }918919 itSub('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (NFT)', async ({helper}) => {920 const collection = await helper.nft.mintCollection(alice);921 const token = await collection.mintToken(alice);922 await testForbidsAddingPropertiesIfPropertyNotDeclared(token, 1n);923 });924925 itSub.ifWithPallets('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (ReFungible)', [Pallets.ReFungible], async ({helper}) => {926 const collection = await helper.rft.mintCollection(alice);927 const token = await collection.mintToken(alice, 100n);928 await testForbidsAddingPropertiesIfPropertyNotDeclared(token, 100n);929 });930931 async function testForbidsAddingTooManyProperties(token: UniqueNFToken | UniqueRFToken, pieces: bigint) {932 const originalSpace = await prepare(token, pieces);933934 await expect(935 token.collection.setTokenPropertyPermissions(alice, [936 {key: 'a_holy_book', permission: {collectionAdmin: true, tokenOwner: true}}, 937 {key: 'young_years', permission: {collectionAdmin: true, tokenOwner: true}},938 ]), 939 'on setting new permissions for properties',940 ).to.be.fulfilled;941942 // Mute the general tx parsing error943 {944 console.error = () => {};945 await expect(token.setProperties(alice, [{key: 'a_holy_book', value: 'word '.repeat(6554)}]))946 .to.be.rejected;947 }948949 await expect(token.setProperties(alice, [950 {key: 'a_holy_book', value: 'word '.repeat(3277)}, 951 {key: 'young_years', value: 'neverending'.repeat(1490)},952 ])).to.be.rejectedWith(/common\.NoSpaceForProperty/);953 954 expect(await token.getProperties(['a_holy_book', 'young_years'])).to.be.empty;955 const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 956 expect(consumedSpace).to.be.equal(originalSpace);957 }958959 itSub('Forbids adding too many properties to a token (NFT)', async ({helper}) => {960 const collection = await helper.nft.mintCollection(alice);961 const token = await collection.mintToken(alice);962 await testForbidsAddingTooManyProperties(token, 1n);963 });964965 itSub.ifWithPallets('Forbids adding too many properties to a token (ReFungible)', [Pallets.ReFungible], async ({helper}) => {966 const collection = await helper.rft.mintCollection(alice);967 const token = await collection.mintToken(alice, 100n);968 await testForbidsAddingTooManyProperties(token, 100n);969 });970});971972describe('ReFungible token properties permissions tests', () => {973 let alice: IKeyringPair;974 let bob: IKeyringPair;975 let charlie: IKeyringPair;976977 before(async function() {978 await usingPlaygrounds(async (helper, privateKey) => {979 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);980981 const donor = privateKey('//Alice');982 [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);983 });984 });985986 async function prepare(helper: UniqueHelper): Promise<UniqueRFToken> {987 const collection = await helper.rft.mintCollection(alice);988 const token = await collection.mintToken(alice, 100n);989 990 await collection.addAdmin(alice, {Substrate: bob.address});991 await collection.setTokenPropertyPermissions(alice, [{key: 'fractals', permission: {mutable: true, tokenOwner: true}}]);992 993 return token;994 }995996 itSub('Forbids adding token property with tokenOwner==true when signer doesn\'t have all pieces', async ({helper}) => {997 const token = await prepare(helper);998999 await token.transfer(alice, {Substrate: charlie.address}, 33n);10001001 await expect(token.setProperties(alice, [1002 {key: 'fractals', value: 'multiverse'}, 1003 ])).to.be.rejectedWith(/common\.NoPermission/);1004 });10051006 itSub('Forbids mutating token property with tokenOwher==true when signer doesn\'t have all pieces', async ({helper}) => {1007 const token = await prepare(helper);10081009 await expect(token.collection.setTokenPropertyPermissions(alice, [{key: 'fractals', permission: {mutable:true, tokenOwner: true}}]))1010 .to.be.fulfilled;10111012 await expect(token.setProperties(alice, [1013 {key: 'fractals', value: 'multiverse'}, 1014 ])).to.be.fulfilled;10151016 await token.transfer(alice, {Substrate: charlie.address}, 33n);10171018 await expect(token.setProperties(alice, [1019 {key: 'fractals', value: 'want to rule the world'}, 1020 ])).to.be.rejectedWith(/common\.NoPermission/);1021 });10221023 itSub('Forbids deleting token property with tokenOwner==true when signer doesn\'t have all pieces', async ({helper}) => {1024 const token = await prepare(helper);10251026 await expect(token.setProperties(alice, [1027 {key: 'fractals', value: 'one headline - why believe it'}, 1028 ])).to.be.fulfilled;10291030 await token.transfer(alice, {Substrate: charlie.address}, 33n);10311032 await expect(token.deleteProperties(alice, ['fractals'])).1033 to.be.rejectedWith(/common\.NoPermission/);1034 });10351036 itSub('Allows token property mutation with collectionOwner==true when admin doesn\'t have all pieces', async ({helper}) => {1037 const token = await prepare(helper);10381039 await token.transfer(alice, {Substrate: charlie.address}, 33n);10401041 await expect(token.collection.setTokenPropertyPermissions(alice, [{key: 'fractals', permission: {mutable:true, collectionAdmin: true}}]))1042 .to.be.fulfilled;10431044 await expect(token.setProperties(alice, [1045 {key: 'fractals', value: 'multiverse'}, 1046 ])).to.be.fulfilled;1047 });1048});tests/src/util/playgrounds/index.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/index.ts
+++ b/tests/src/util/playgrounds/index.ts
@@ -12,13 +12,6 @@
chai.use(chaiAsPromised);
export const expect = chai.expect;
-export const U128_MAX = (1n << 128n) - 1n;
-
-const MICROUNIQUE = 1_000_000_000_000n;
-const MILLIUNIQUE = 1_000n * MICROUNIQUE;
-const CENTIUNIQUE = 10n * MILLIUNIQUE;
-export const UNIQUE = 100n * CENTIUNIQUE;
-
export const usingPlaygrounds = async (code: (helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair) => Promise<void>, url: string = config.substrateUrl) => {
const silentConsole = new SilentConsole();
silentConsole.enable();
tests/src/util/playgrounds/types.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/types.ts
+++ b/tests/src/util/playgrounds/types.ts
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
import {IKeyringPair} from '@polkadot/types/types';
-import {UniqueHelper} from './unique';
export interface IEvent {
section: string;
@@ -107,7 +106,7 @@
}
}
-export interface ITokenAddress {
+export interface IToken {
collectionId: number;
tokenId: number;
}
@@ -168,169 +167,3 @@
export type TApiAllowedListeners = 'connected' | 'disconnected' | 'error' | 'ready' | 'decorated';
export type TUniqueNetworks = 'opal' | 'quartz' | 'unique';
export type TSigner = IKeyringPair; // | 'string'
-
-export interface ICollectionBase {
- helper: UniqueHelper;
- collectionId: number;
- getData: () => Promise<{
- id: number;
- name: string;
- description: string;
- tokensCount: number;
- admins: ICrossAccountId[];
- normalizedOwner: TSubstrateAccount;
- raw: any
- } | null>;
- getLastTokenId: () => Promise<number>;
- isTokenExists: (tokenId: number) => Promise<boolean>;
- getAdmins: () => Promise<ICrossAccountId[]>;
- getAllowList: () => Promise<ICrossAccountId[]>;
- getEffectiveLimits: () => Promise<ICollectionLimits>;
- getProperties: (propertyKeys?: string[]) => Promise<IProperty[]>;
- getTokenNextSponsored: (tokenId: number, addressObj: ICrossAccountId) => Promise<number | null>;
- setSponsor: (signer: TSigner, sponsorAddress: TSubstrateAccount) => Promise<boolean>;
- confirmSponsorship: (signer: TSigner) => Promise<boolean>;
- removeSponsor: (signer: TSigner) => Promise<boolean>;
- setLimits: (signer: TSigner, limits: ICollectionLimits) => Promise<boolean>;
- changeOwner: (signer: TSigner, ownerAddress: TSubstrateAccount) => Promise<boolean>;
- addAdmin: (signer: TSigner, adminAddressObj: ICrossAccountId) => Promise<boolean>;
- addToAllowList: (signer: TSigner, addressObj: ICrossAccountId) => Promise<boolean>;
- removeFromAllowList: (signer: TSigner, addressObj: ICrossAccountId) => Promise<boolean>;
- removeAdmin: (signer: TSigner, adminAddressObj: ICrossAccountId) => Promise<boolean>;
- setProperties: (signer: TSigner, properties: IProperty[]) => Promise<boolean>;
- deleteProperties: (signer: TSigner, propertyKeys: string[]) => Promise<boolean>;
- setPermissions: (signer: TSigner, permissions: ICollectionPermissions) => Promise<boolean>;
- enableNesting: (signer: TSigner, permissions: INestingPermissions) => Promise<boolean>;
- disableNesting: (signer: TSigner) => Promise<boolean>;
- burn: (signer: TSigner) => Promise<boolean>;
-}
-
-export interface ICollectionNFT extends ICollectionBase {
- getTokenObject: (tokenId: number) => ITokenNonfungible; // todo:playgrounds
- getTokensByAddress: (addressObj: ICrossAccountId) => Promise<number[]>;
- getToken: (tokenId: number, blockHashAt?: string) => Promise<{
- properties: IProperty[];
- owner: ICrossAccountId;
- normalizedOwner: ICrossAccountId;
- }| null>;
- getTokenOwner: (tokenId: number, blockHashAt?: string) => Promise<ICrossAccountId>;
- getTokenTopmostOwner: (tokenId: number, blockHashAt?: string) => Promise<ICrossAccountId | null>;
- getTokenChildren: (tokenId: number, blockHashAt?: string) => Promise<ITokenAddress[]>; // todo:playgrounds
- getPropertyPermissions: (propertyKeys?: string[]) => Promise<ITokenPropertyPermission[]>;
- getTokenProperties: (tokenId: number, propertyKeys?: string[]) => Promise<IProperty[]>;
- transferToken: (signer: TSigner, tokenId: number, addressObj: ICrossAccountId) => Promise<boolean>;
- transferTokenFrom: (signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) => Promise<boolean>;
- approveToken: (signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId) => Promise<boolean>;
- isTokenApproved: (tokenId: number, toAddressObj: ICrossAccountId) => Promise<boolean>;
- mintToken: (signer: TSigner, owner: ICrossAccountId, properties?: IProperty[]) => Promise<ITokenNonfungible>;// todo:playgrounds
- mintMultipleTokens: (signer: TSigner, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[]) => Promise<ITokenNonfungible[]>;// todo:playgrounds
- burnToken: (signer: TSigner, tokenId: number) => Promise<{
- success: boolean,
- token: number | null
- }>;
- burnTokenFrom: (signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId) => Promise<boolean>;
- setTokenProperties: (signer: TSigner, tokenId: number, properties: IProperty[]) => Promise<boolean>;
- deleteTokenProperties: (signer: TSigner, tokenId: number, propertyKeys: string[]) => Promise<boolean>;
- setTokenPropertyPermissions: (signer: TSigner, permissions: ITokenPropertyPermission[]) => Promise<boolean>;
- nestToken: (signer: TSigner, tokenId: number, toTokenObj: ITokenAddress) => Promise<boolean>;
- unnestToken: (signer: TSigner, tokenId: number, fromTokenObj: ITokenAddress, toAddressObj: ICrossAccountId) => Promise<boolean>;
-}
-
-export interface ICollectionRFT extends ICollectionBase {
- getTokenObject: (tokenId: number) => ITokenRefungible;
- getToken: (tokenId: number, blockHashAt?: string) => Promise<{
- properties: IProperty[];
- owner: ICrossAccountId;
- normalizedOwner: ICrossAccountId;
- }| null>;
- getTokensByAddress: (addressObj: ICrossAccountId) => Promise<number[]>;
- getTop10TokenOwners: (tokenId: number) => Promise<ICrossAccountId[]>;
- getTokenBalance: (tokenId: number, addressObj: ICrossAccountId) => Promise<bigint>;
- getTokenTotalPieces: (tokenId: number) => Promise<bigint>;
- getTokenApprovedPieces: (tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) => Promise<bigint>;
- getPropertyPermissions: (propertyKeys?: string[] | null) => Promise<ITokenPropertyPermission[]>;
- getTokenProperties: (tokenId: number, propertyKeys?: string[]) => Promise<IProperty[]>;
- transferToken: (signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount: bigint) => Promise<boolean>;
- transferTokenFrom: (signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount: bigint) => Promise<boolean>;
- approveToken: (signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount: bigint) => Promise<boolean>;
- repartitionToken: (signer: TSigner, tokenId: number, amount: bigint) => Promise<boolean>;
- mintToken: (signer: TSigner, pieces: bigint, owner: ICrossAccountId, properties?: IProperty[]) => Promise<ITokenRefungible>;
- mintMultipleTokens: (signer: TSigner, tokens: {pieces: bigint, owner: ICrossAccountId, properties?: IProperty[]}[]) => Promise<ITokenRefungible[]>; // todo:playgrounds
- burnToken: (signer: TSigner, tokenId: number, amount: bigint) => Promise<{
- success: boolean,
- token: number | null
- }>;
- burnTokenFrom: (signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, amount: bigint) => Promise<boolean>;
- setTokenProperties: (signer: TSigner, tokenId: number, properties: IProperty[]) => Promise<boolean>;
- deleteTokenProperties: (signer: TSigner, tokenId: number, propertyKeys: string[]) => Promise<boolean>;
- setTokenPropertyPermissions: (signer: TSigner, permissions: ITokenPropertyPermission[]) => Promise<boolean>;
-}
-
-export interface ICollectionFT extends ICollectionBase {
- getBalance: (addressObj: ICrossAccountId) => Promise<bigint>;
- getTotalPieces: () => Promise<bigint>;
- getApprovedTokens: (fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) => Promise<bigint>;
- getTop10Owners: () => Promise<ICrossAccountId[]>;
- mint: (signer: TSigner, amount: bigint, owner: ICrossAccountId) => Promise<boolean>;
- mintWithOneOwner: (signer: TSigner, tokens: {value: bigint}[], owner: ICrossAccountId) => Promise<boolean>;
- transfer: (signer: TSigner, toAddressObj: ICrossAccountId, amount: bigint) => Promise<boolean>;
- transferFrom: (signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount: bigint) => Promise<boolean>;
- burnTokens: (signer: TSigner, amount: bigint) => Promise<boolean>;
- burnTokensFrom: (signer: TSigner, fromAddressObj: ICrossAccountId, amount: bigint) => Promise<boolean>;
- approveTokens: (signer: TSigner, toAddressObj: ICrossAccountId, amount: bigint) => Promise<boolean>;
-}
-
-export interface ITokenBase extends ITokenAddress {
- collection: ICollectionNFT | ICollectionRFT;
- getNextSponsored: (addressObj: ICrossAccountId) => Promise<number|null>;
- getProperties: (propertyKeys?: string[]) => Promise<IProperty[]>;
- setProperties: (signer: TSigner, properties: IProperty[]) => Promise<boolean>;
- deleteProperties: (signer: TSigner, propertyKeys: string[]) => Promise<boolean>;
- nestingAccount: () => ICrossAccountId;
- nestingAccountInLowerCase: () => ICrossAccountId;
-}
-
-export interface ITokenNonfungible extends ITokenBase {
- collection: ICollectionNFT;
- getData: (blockHashAt?: string) => Promise<{
- properties: IProperty[];
- owner: ICrossAccountId;
- normalizedOwner: ICrossAccountId;
- }| null>;
- getOwner: (blockHashAt?: string) => Promise<ICrossAccountId>;
- getTopmostOwner: (blockHashAt?: string) => Promise<ICrossAccountId | null>;
- getChildren: (blockHashAt?: string) => Promise<ITokenAddress[]>; // todo:playgrounds
- nest: (signer: TSigner, toTokenObj: ITokenAddress) => Promise<boolean>;
- unnest: (signer: TSigner, fromTokenObj: ITokenAddress, toAddressObj: ICrossAccountId) => Promise<boolean>;
- transfer: (signer: TSigner, addressObj: ICrossAccountId) => Promise<boolean>;
- transferFrom: (signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) => Promise<boolean>;
- approve: (signer: TSigner, toAddressObj: ICrossAccountId) => Promise<boolean>;
- isApproved: (toAddressObj: ICrossAccountId) => Promise<boolean>;
- burn: (signer: TSigner) => Promise<{
- success: boolean,
- token: number | null
- }>;
- burnFrom: (signer: TSigner, fromAddressObj: ICrossAccountId) => Promise<boolean>;
-}
-
-export interface ITokenRefungible extends ITokenBase {
- collection: ICollectionRFT;
- getData: (blockHashAt?: string) => Promise<{
- properties: IProperty[];
- owner: ICrossAccountId;
- normalizedOwner: ICrossAccountId;
- }| null>;
- getTop10Owners: () => Promise<ICrossAccountId[]>;
- getBalance: (addressObj: ICrossAccountId) => Promise<bigint>;
- getTotalPieces: () => Promise<bigint>;
- getApprovedPieces: (fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) => Promise<bigint>;
- transfer: (signer: TSigner, addressObj: ICrossAccountId, amount: bigint) => Promise<boolean>;
- transferFrom: (signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount: bigint) => Promise<boolean>;
- approve: (signer: TSigner, toAddressObj: ICrossAccountId, amount: bigint) => Promise<boolean>;
- repartition: (signer: TSigner, amount: bigint) => Promise<boolean>;
- burn: (signer: TSigner, amount: bigint) => Promise<{
- success: boolean,
- token: number | null
- }>;
- burnFrom: (signer: TSigner, fromAddressObj: ICrossAccountId, amount: bigint) => Promise<boolean>;
-}
tests/src/util/playgrounds/unique.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/unique.ts
+++ b/tests/src/util/playgrounds/unique.ts
@@ -9,7 +9,7 @@
import {ApiInterfaceEvents, SignerOptions} from '@polkadot/api/types';
import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm} from '@polkadot/util-crypto';
import {IKeyringPair} from '@polkadot/types/types';
-import {IApiListeners, IBlock, IEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, IStakingInfo, ISubstrateBalance, ITokenAddress, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, TUniqueNetworks, ICollectionBase, ICollectionNFT, ICollectionRFT, ITokenBase, ITokenNonfungible, ITokenRefungible, ICollectionFT} from './types';
+import {IApiListeners, IBlock, IEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, IStakingInfo, ISubstrateBalance, IToken, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, TUniqueNetworks} from './types';
export const crossAccountIdFromLower = (lowerAddress: ICrossAccountIdLower): ICrossAccountId => {
const address = {} as ICrossAccountId;
@@ -55,15 +55,15 @@
RPC: 'rpc',
};
- static getTokenAccount(token: ITokenAddress): ICrossAccountId {
+ static getTokenAccount(token: IToken): ICrossAccountId {
return {Ethereum: this.getTokenAddress(token)};
}
- static getTokenAccountInLowerCase(token: ITokenAddress): ICrossAccountId {
+ static getTokenAccountInLowerCase(token: IToken): ICrossAccountId {
return {Ethereum: this.getTokenAddress(token).toLowerCase()};
}
- static getTokenAddress(token: ITokenAddress): string {
+ static getTokenAddress(token: IToken): string {
return nesting.tokenIdToAddress(token.collectionId, token.tokenId);
}
@@ -1218,7 +1218,7 @@
* @example mintCollection(aliceKeyring, {name: 'New', description: "New collection", tokenPrefix: "NEW"}, "NFT")
* @returns object of the created collection
*/
- async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT'): Promise<UniqueCollectionBase> {
+ async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT'): Promise<UniqueBaseCollection> {
collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object
collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};
for (const key of ['name', 'description', 'tokenPrefix']) {
@@ -1260,8 +1260,8 @@
* @example getTokenObject(10, 5);
* @returns instance of UniqueNFTToken
*/
- getTokenObject(collectionId: number, tokenId: number): UniqueNFTToken {
- return new UniqueNFTToken(tokenId, this.getCollectionObject(collectionId));
+ getTokenObject(collectionId: number, tokenId: number): UniqueNFToken {
+ return new UniqueNFToken(tokenId, this.getCollectionObject(collectionId));
}
/**
@@ -1352,7 +1352,7 @@
* @example getTokenChildren(10, 5);
* @returns tokens whose depth of nesting is <= 5
*/
- async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise<ITokenAddress[]> {
+ async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise<IToken[]> {
let children;
if(typeof blockHashAt === 'undefined') {
children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId]);
@@ -1373,7 +1373,7 @@
* @example nestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4});
* @returns ```true``` if extrinsic success, otherwise ```false```
*/
- async nestToken(signer: TSigner, tokenObj: ITokenAddress, rootTokenObj: ITokenAddress): Promise<boolean> {
+ async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken): Promise<boolean> {
const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);
const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);
if(!result) {
@@ -1391,7 +1391,7 @@
* @example unnestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}, {Substrate: "5DyN4Y92vZCjv38fg..."});
* @returns ```true``` if extrinsic success, otherwise ```false```
*/
- async unnestToken(signer: TSigner, tokenObj: ITokenAddress, rootTokenObj: ITokenAddress, toAddressObj: ICrossAccountId): Promise<boolean> {
+ async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId): Promise<boolean> {
const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);
const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);
if(!result) {
@@ -1422,7 +1422,7 @@
* @param data token data
* @returns created token object
*/
- async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }): Promise<UniqueNFTToken> {
+ async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }): Promise<UniqueNFToken> {
const creationResult = await this.helper.executeExtrinsic(
signer,
'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {
@@ -1453,14 +1453,14 @@
* }]);
* @returns ```true``` if extrinsic success, otherwise ```false```
*/
- async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[]): Promise<UniqueNFTToken[]> {
+ async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[]): Promise<UniqueNFToken[]> {
const creationResult = await this.helper.executeExtrinsic(
signer,
'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}],
true,
);
const collection = this.getCollectionObject(collectionId);
- return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: ITokenAddress) => collection.getTokenObject(x.tokenId));
+ return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));
}
/**
@@ -1481,7 +1481,7 @@
* }]);
* @returns array of newly created tokens
*/
- async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {properties?: IProperty[]}[]): Promise<UniqueNFTToken[]> {
+ async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {properties?: IProperty[]}[]): Promise<UniqueNFToken[]> {
const rawTokens = [];
for (const token of tokens) {
const raw = {NFT: {properties: token.properties}};
@@ -1493,7 +1493,7 @@
true,
);
const collection = this.getCollectionObject(collectionId);
- return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: ITokenAddress) => collection.getTokenObject(x.tokenId));
+ return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));
}
/**
@@ -1530,8 +1530,8 @@
* @example getTokenObject(10, 5);
* @returns instance of UniqueNFTToken
*/
- getTokenObject(collectionId: number, tokenId: number): UniqueRFTToken {
- return new UniqueRFTToken(tokenId, this.getCollectionObject(collectionId));
+ getTokenObject(collectionId: number, tokenId: number): UniqueRFToken {
+ return new UniqueRFToken(tokenId, this.getCollectionObject(collectionId));
}
/**
@@ -1609,7 +1609,7 @@
* @example mintToken(aliceKeyring, {collectionId: 10, owner: {Substrate: '5GHoZe9c73RYbVzq...'}, pieces: 10000n});
* @returns created token object
*/
- async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }): Promise<UniqueRFTToken> {
+ async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }): Promise<UniqueRFToken> {
const creationResult = await this.helper.executeExtrinsic(
signer,
'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {
@@ -1626,7 +1626,7 @@
return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);
}
- async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[]): Promise<UniqueRFTToken[]> {
+ async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[]): Promise<UniqueRFToken[]> {
throw Error('Not implemented');
const creationResult = await this.helper.executeExtrinsic(
signer,
@@ -1634,7 +1634,7 @@
true, // `Unable to mint RFT tokens for ${label}`,
);
const collection = this.getCollectionObject(collectionId);
- return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: ITokenAddress) => collection.getTokenObject(x.tokenId));
+ return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));
}
/**
@@ -1646,7 +1646,7 @@
* @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]);
* @returns array of newly created RFT tokens
*/
- async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {pieces: bigint, properties?: IProperty[]}[]): Promise<UniqueRFTToken[]> {
+ async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {pieces: bigint, properties?: IProperty[]}[]): Promise<UniqueRFToken[]> {
const rawTokens = [];
for (const token of tokens) {
const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}};
@@ -1658,7 +1658,7 @@
true,
);
const collection = this.getCollectionObject(collectionId);
- return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: ITokenAddress) => collection.getTokenObject(x.tokenId));
+ return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));
}
/**
@@ -1985,7 +1985,7 @@
class BalanceGroup extends HelperGroup {
/**
- * Representation of the native token in the smallest unit
+ * Representation of the native token in the smallest unit - one OPAL (OPL), QUARTZ (QTZ), or UNIQUE (UNQ).
* @example getOneTokenNominal()
* @returns ```BigInt``` representation of the native token in the smallest unit, e.g. ```1_000_000_000_000_000_000n``` for QTZ.
*/
@@ -2223,7 +2223,7 @@
}
-class UniqueCollectionBase implements ICollectionBase {
+export class UniqueBaseCollection {
helper: UniqueHelper;
collectionId: number;
@@ -2326,9 +2326,9 @@
}
-class UniqueNFTCollection extends UniqueCollectionBase implements ICollectionNFT {
+export class UniqueNFTCollection extends UniqueBaseCollection {
getTokenObject(tokenId: number) {
- return new UniqueNFTToken(tokenId, this);
+ return new UniqueNFToken(tokenId, this);
}
async getTokensByAddress(addressObj: ICrossAccountId) {
@@ -2403,19 +2403,19 @@
return await this.helper.nft.setTokenPropertyPermissions(signer, this.collectionId, permissions);
}
- async nestToken(signer: TSigner, tokenId: number, toTokenObj: ITokenAddress) {
+ async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken) {
return await this.helper.nft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj);
}
- async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: ITokenAddress, toAddressObj: ICrossAccountId) {
+ async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {
return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);
}
}
-class UniqueRFTCollection extends UniqueCollectionBase implements ICollectionRFT {
+export class UniqueRFTCollection extends UniqueBaseCollection {
getTokenObject(tokenId: number) {
- return new UniqueRFTToken(tokenId, this);
+ return new UniqueRFToken(tokenId, this);
}
async getToken(tokenId: number, blockHashAt?: string) {
@@ -2496,7 +2496,7 @@
}
-class UniqueFTCollection extends UniqueCollectionBase implements ICollectionFT {
+export class UniqueFTCollection extends UniqueBaseCollection {
async getBalance(addressObj: ICrossAccountId) {
return await this.helper.ft.getBalance(this.collectionId, addressObj);
}
@@ -2543,7 +2543,7 @@
}
-class UniqueTokenBase implements ITokenBase {
+export class UniqueBaseToken {
collection: UniqueNFTCollection | UniqueRFTCollection;
collectionId: number;
tokenId: number;
@@ -2580,7 +2580,7 @@
}
-class UniqueNFTToken extends UniqueTokenBase implements ITokenNonfungible {
+export class UniqueNFToken extends UniqueBaseToken {
collection: UniqueNFTCollection;
constructor(tokenId: number, collection: UniqueNFTCollection) {
@@ -2604,11 +2604,11 @@
return await this.collection.getTokenChildren(this.tokenId, blockHashAt);
}
- async nest(signer: TSigner, toTokenObj: ITokenAddress) {
+ async nest(signer: TSigner, toTokenObj: IToken) {
return await this.collection.nestToken(signer, this.tokenId, toTokenObj);
}
- async unnest(signer: TSigner, fromTokenObj: ITokenAddress, toAddressObj: ICrossAccountId) {
+ async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {
return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj);
}
@@ -2637,7 +2637,7 @@
}
}
-class UniqueRFTToken extends UniqueTokenBase implements ITokenRefungible {
+export class UniqueRFToken extends UniqueBaseToken {
collection: UniqueRFTCollection;
constructor(tokenId: number, collection: UniqueRFTCollection) {