difftreelog
tests(nesting+properties): collection and token interfaces + make lower case not default for token addresses + more consts from old helpers included
in: master
8 files changed
tests/src/fungible.test.tsdiffbeforeafterboth--- a/tests/src/fungible.test.ts
+++ b/tests/src/fungible.test.ts
@@ -15,9 +15,7 @@
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
import {IKeyringPair} from '@polkadot/types/types';
-import {itSub, usingPlaygrounds, expect} from './util/playgrounds';
-
-const U128_MAX = (1n << 128n) - 1n;
+import {itSub, usingPlaygrounds, expect, U128_MAX} from './util/playgrounds';
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,7 +16,8 @@
import {IKeyringPair} from '@polkadot/types/types';
import {expect, itSub, usingPlaygrounds} from '../util/playgrounds';
-import {UniqueHelper, UniqueNFTToken} from '../util/playgrounds/unique';
+import {ITokenNonfungible} from '../util/playgrounds/types';
+import {UniqueHelper} from '../util/playgrounds/unique';
/**
* ```dot
@@ -25,7 +26,7 @@
* 8 -> 5
* ```
*/
-async function buildComplexObjectGraph(helper: UniqueHelper, sender: IKeyringPair): Promise<UniqueNFTToken[]> {
+async function buildComplexObjectGraph(helper: UniqueHelper, sender: IKeyringPair): Promise<ITokenNonfungible[]> {
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/nest.test.tsdiffbeforeafterboth--- a/tests/src/nesting/nest.test.ts
+++ b/tests/src/nesting/nest.test.ts
@@ -35,7 +35,7 @@
// Create an immediately nested token
const nestedToken = await collection.mintToken(alice, targetToken.nestingAccount());
expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});
- expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount());
+ expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());
// Create a token to be nested
const newToken = await collection.mintToken(alice);
@@ -43,14 +43,14 @@
// Nest
await newToken.nest(alice, targetToken);
expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});
- expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount());
+ expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());
// Move bundle to different user
await targetToken.transfer(alice, {Substrate: bob.address});
expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});
- expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount());
+ expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());
expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});
- expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount());
+ expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());
// Unnest
await newToken.unnest(bob, targetToken, {Substrate: bob.address});
@@ -65,12 +65,12 @@
// Create a nested token
const tokenC = await collection.mintToken(alice, tokenA.nestingAccount());
- expect(await tokenC.getOwner()).to.be.deep.equal(tokenA.nestingAccount());
+ expect(await tokenC.getOwner()).to.be.deep.equal(tokenA.nestingAccountInLowerCase());
// Transfer the nested token to another token
await expect(tokenC.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount())).to.be.fulfilled;
expect(await tokenC.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});
- expect(await tokenC.getOwner()).to.be.deep.equal(tokenB.nestingAccount());
+ expect(await tokenC.getOwner()).to.be.deep.equal(tokenB.nestingAccountInLowerCase());
});
itSub('Checks token children', async ({helper}) => {
@@ -150,13 +150,13 @@
// Create an immediately nested token
const nestedToken = await collection.mintToken(bob, targetToken.nestingAccount());
expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});
- expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount());
+ expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());
// Create a token to be nested and nest
const newToken = await collection.mintToken(bob);
await newToken.nest(bob, targetToken);
expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});
- expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount());
+ expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());
});
itSub('Admin (NFT): Admin and Token Owner can operate together', async ({helper}) => {
@@ -167,13 +167,13 @@
// Create an immediately nested token by an administrator
const nestedToken = await collection.mintToken(bob, targetToken.nestingAccount());
expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});
- expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount());
+ expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());
// Create a token to be nested and nest
const newToken = await collection.mintToken(alice, {Substrate: charlie.address});
await newToken.nest(charlie, targetToken);
expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});
- expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount());
+ expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());
});
itSub('Admin (NFT): allows an Admin to nest a token (Restricted nesting)', async ({helper}) => {
@@ -187,13 +187,13 @@
// Create an immediately nested token
const nestedToken = await collectionB.mintToken(bob, targetToken.nestingAccount());
expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});
- expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount());
+ expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());
// Create a token to be nested and nest
const newToken = await collectionB.mintToken(bob);
await newToken.nest(bob, targetToken);
expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});
- expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount());
+ expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());
});
// ---------- Non-Fungible ----------
@@ -207,13 +207,13 @@
// Create an immediately nested token
const nestedToken = await collection.mintToken(charlie, targetToken.nestingAccount());
expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});
- expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount());
+ expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());
// Create a token to be nested and nest
const newToken = await collection.mintToken(charlie);
await newToken.nest(charlie, targetToken);
expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});
- expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount());
+ expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());
});
itSub('NFT: allows an Owner to nest/unnest their token (Restricted nesting)', async ({helper}) => {
@@ -233,13 +233,13 @@
// Create an immediately nested token
const nestedToken = await collectionB.mintToken(charlie, targetToken.nestingAccount());
expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});
- expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount());
+ expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());
// Create a token to be nested and nest
const newToken = await collectionB.mintToken(charlie);
await newToken.nest(charlie, targetToken);
expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});
- expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount());
+ expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());
});
// ---------- Fungible ----------
@@ -424,7 +424,7 @@
expect(await targetToken.getChildren()).to.be.length(1);
expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});
- expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount());
+ expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());
});
itSub('Admin (NFT): disallows an Admin to nest a token from an unlisted collection (Restricted nesting)', async ({helper}) => {
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/>.1617/*import usingApi, {executeTransaction} from '../substrate/substrate-api';18import {19 addCollectionAdminExpectSuccess,20 CollectionMode,21 createCollectionExpectSuccess,22 setCollectionPermissionsExpectSuccess,23 createItemExpectSuccess,24 getCreateCollectionResult,25 transferExpectSuccess,26} from '../util/helpers';*/27import {IKeyringPair} from '@polkadot/types/types';28import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '../util/playgrounds';29import {UniqueCollectionBase, UniqueHelper, UniqueNFTCollection, UniqueNFTToken, UniqueRFTCollection, UniqueRFTToken} from '../util/playgrounds/unique';3031// ---------- COLLECTION PROPERTIES3233describe('Integration Test: Collection Properties', () => {34 let alice: IKeyringPair;35 let bob: IKeyringPair;3637 before(async () => {38 await usingPlaygrounds(async (helper, privateKey) => {39 const donor = privateKey('//Alice');40 [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);41 });42 });4344 itSub('Properties are initially empty', async ({helper}) => {45 const collection = await helper.nft.mintCollection(alice);46 expect(await collection.getProperties()).to.be.empty;47 });4849 async function testSetsPropertiesForCollection(collection: UniqueCollectionBase) {50 // As owner51 await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}])).to.be.fulfilled;5253 await collection.addAdmin(alice, {Substrate: bob.address});5455 // As administrator56 await expect(collection.setProperties(bob, [{key: 'black_hole'}])).to.be.fulfilled;5758 const properties = await collection.getProperties();59 expect(properties).to.include.deep.members([60 {key: 'electron', value: 'come bond'},61 {key: 'black_hole', value: ''},62 ]);63 }6465 itSub('Sets properties for a NFT collection', async ({helper}) => {66 await testSetsPropertiesForCollection(await helper.nft.mintCollection(alice));67 });6869 itSub.ifWithPallets('Sets properties for a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {70 await testSetsPropertiesForCollection(await helper.rft.mintCollection(alice));71 });7273 async function testCheckValidNames(collection: UniqueCollectionBase) {74 // alpha symbols75 await expect(collection.setProperties(alice, [{key: 'answer'}])).to.be.fulfilled;7677 // numeric symbols78 await expect(collection.setProperties(alice, [{key: '451'}])).to.be.fulfilled;7980 // underscore symbol81 await expect(collection.setProperties(alice, [{key: 'black_hole'}])).to.be.fulfilled;8283 // dash symbol84 await expect(collection.setProperties(alice, [{key: '-'}])).to.be.fulfilled;8586 // dot symbol87 await expect(collection.setProperties(alice, [{key: 'once.in.a.long.long.while...', value: 'you get a little lost'}])).to.be.fulfilled;8889 const properties = await collection.getProperties();90 expect(properties).to.include.deep.members([91 {key: 'answer', value: ''},92 {key: '451', value: ''},93 {key: 'black_hole', value: ''},94 {key: '-', value: ''},95 {key: 'once.in.a.long.long.while...', value: 'you get a little lost'},96 ]);97 }9899 itSub('Check valid names for NFT collection properties keys', async ({helper}) => {100 await testCheckValidNames(await helper.nft.mintCollection(alice));101 });102103 itSub.ifWithPallets('Check valid names for ReFungible collection properties keys', [Pallets.ReFungible], async ({helper}) => {104 await testCheckValidNames(await helper.rft.mintCollection(alice));105 });106107 async function testChangesProperties(collection: UniqueCollectionBase) {108 await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: ''}])).to.be.fulfilled;109110 // Mutate the properties111 await expect(collection.setProperties(alice, [{key: 'black_hole', value: 'LIGO'}])).to.be.fulfilled;112113 const properties = await collection.getProperties();114 expect(properties).to.include.deep.members([115 {key: 'electron', value: 'come bond'},116 {key: 'black_hole', value: 'LIGO'},117 ]);118 }119120 itSub('Changes properties of a NFT collection', async ({helper}) => {121 await testChangesProperties(await helper.nft.mintCollection(alice));122 });123124 itSub.ifWithPallets('Changes properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {125 await testChangesProperties(await helper.rft.mintCollection(alice));126 });127128 async function testDeleteProperties(collection: UniqueCollectionBase) {129 await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}])).to.be.fulfilled;130131 await expect(collection.deleteProperties(alice, ['electron'])).to.be.fulfilled;132133 const properties = await collection.getProperties(['black_hole', 'electron']);134 expect(properties).to.be.deep.equal([135 {key: 'black_hole', value: 'LIGO'},136 ]);137 }138139 itSub('Deletes properties of a NFT collection', async ({helper}) => {140 await testDeleteProperties(await helper.nft.mintCollection(alice));141 });142143 itSub.ifWithPallets('Deletes properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {144 await testDeleteProperties(await helper.rft.mintCollection(alice));145 });146});147148describe('Negative Integration Test: Collection Properties', () => {149 let alice: IKeyringPair;150 let bob: IKeyringPair;151152 before(async () => {153 await usingPlaygrounds(async (helper, privateKey) => {154 const donor = privateKey('//Alice');155 [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor);156 });157 });158 159 async function testFailsSetPropertiesIfNotOwnerOrAdmin(collection: UniqueCollectionBase) { 160 await expect(collection.setProperties(bob, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]))161 .to.be.rejectedWith(/common\.NoPermission/);162163 expect(await collection.getProperties()).to.be.empty;164 }165166 itSub('Fails to set properties in a NFT collection if not its onwer/administrator', async ({helper}) => {167 await testFailsSetPropertiesIfNotOwnerOrAdmin(await helper.nft.mintCollection(alice));168 });169170 itSub.ifWithPallets('Fails to set properties in a ReFungible collection if not its onwer/administrator', [Pallets.ReFungible], async ({helper}) => {171 await testFailsSetPropertiesIfNotOwnerOrAdmin(await helper.rft.mintCollection(alice));172 });173 174 async function testFailsSetPropertiesThatExeedLimits(collection: UniqueCollectionBase) {175 const spaceLimit = (await (collection.helper!.api! as any).query.common.collectionProperties(collection.collectionId)).spaceLimit.toNumber();176 177 // Mute the general tx parsing error, too many bytes to process178 {179 console.error = () => {};180 await expect(collection.setProperties(alice, [181 {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 9))},182 ])).to.be.rejected;183 }184185 expect(await collection.getProperties(['electron'])).to.be.empty;186187 await expect(collection.setProperties(alice, [188 {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 18))}, 189 {key: 'black_hole', value: '0'.repeat(Math.ceil(spaceLimit! / 2))}, 190 ])).to.be.rejectedWith(/common\.NoSpaceForProperty/);191192 expect(await collection.getProperties(['electron', 'black_hole'])).to.be.empty;193 }194195 itSub('Fails to set properties that exceed the limits (NFT)', async ({helper}) => {196 await testFailsSetPropertiesThatExeedLimits(await helper.nft.mintCollection(alice));197 });198199 itSub.ifWithPallets('Fails to set properties that exceed the limits (ReFungible)', [Pallets.ReFungible], async ({helper}) => {200 await testFailsSetPropertiesThatExeedLimits(await helper.rft.mintCollection(alice));201 });202 203 async function testFailsSetMorePropertiesThanAllowed(collection: UniqueCollectionBase) {204 const propertiesToBeSet = [];205 for (let i = 0; i < 65; i++) {206 propertiesToBeSet.push({207 key: 'electron_' + i,208 value: Math.random() > 0.5 ? 'high' : 'low',209 });210 }211212 await expect(collection.setProperties(alice, propertiesToBeSet)).213 to.be.rejectedWith(/common\.PropertyLimitReached/);214215 expect(await collection.getProperties()).to.be.empty;216 }217218 itSub('Fails to set more properties than it is allowed (NFT)', async ({helper}) => {219 await testFailsSetMorePropertiesThanAllowed(await helper.nft.mintCollection(alice));220 });221222 itSub.ifWithPallets('Fails to set more properties than it is allowed (ReFungible)', [Pallets.ReFungible], async ({helper}) => {223 await testFailsSetMorePropertiesThanAllowed(await helper.rft.mintCollection(alice));224 });225 226 async function testFailsSetPropertiesWithInvalidNames(collection: UniqueCollectionBase) {227 const invalidProperties = [228 [{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}],229 [{key: 'Mr/Sandman', value: 'Bring me a gene'}],230 [{key: 'déjà vu', value: 'hmm...'}],231 ];232233 for (let i = 0; i < invalidProperties.length; i++) {234 await expect(235 collection.setProperties(alice, invalidProperties[i]), 236 `on rejecting the new badly-named property #${i}`,237 ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);238 }239240 await expect(241 collection.setProperties(alice, [{key: '', value: 'nothing must not exist'}]), 242 'on rejecting an unnamed property',243 ).to.be.rejectedWith(/common\.EmptyPropertyKey/);244245 await expect(246 collection.setProperties(alice, [{key: 'CRISPR-Cas9', value: 'rewriting nature!'}]), 247 'on setting the correctly-but-still-badly-named property',248 ).to.be.fulfilled;249250 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat('CRISPR-Cas9').concat('');251252 const properties = await collection.getProperties(keys);253 expect(properties).to.be.deep.equal([254 {key: 'CRISPR-Cas9', value: 'rewriting nature!'},255 ]);256257 for (let i = 0; i < invalidProperties.length; i++) {258 await expect(259 collection.deleteProperties(alice, invalidProperties[i].map(propertySet => propertySet.key)), 260 `on trying to delete the non-existent badly-named property #${i}`,261 ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);262 }263 }264265 itSub('Fails to set properties with invalid names (NFT)', async ({helper}) => {266 await testFailsSetPropertiesWithInvalidNames(await helper.nft.mintCollection(alice));267 });268269 itSub.ifWithPallets('Fails to set properties with invalid names (ReFungible)', [Pallets.ReFungible], async ({helper}) => {270 await testFailsSetPropertiesWithInvalidNames(await helper.rft.mintCollection(alice));271 });272});273274// ---------- ACCESS RIGHTS275276describe('Integration Test: Access Rights to Token Properties', () => {277 let alice: IKeyringPair;278 let bob: IKeyringPair;279280 before(async () => {281 await usingPlaygrounds(async (helper, privateKey) => {282 const donor = privateKey('//Alice');283 [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor);284 });285 });286 287 itSub('Reads access rights to properties of a collection', async ({helper}) => {288 const collection = await helper.nft.mintCollection(alice);289 const propertyRights = (await helper.api!.query.common.collectionPropertyPermissions(collection.collectionId)).toJSON();290 expect(propertyRights).to.be.empty;291 });292 293 async function testSetsAccessRightsToProperties(collection: UniqueNFTCollection | UniqueRFTCollection) { 294 await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: true}}]))295 .to.be.fulfilled;296297 await collection.addAdmin(alice, {Substrate: bob.address});298299 await expect(collection.setTokenPropertyPermissions(bob, [{key: 'mindgame', permission: {collectionAdmin: true, tokenOwner: false}}]))300 .to.be.fulfilled;301302 const propertyRights = await collection.getPropertyPermissions(['skullduggery', 'mindgame']);303 expect(propertyRights).to.include.deep.members([304 {key: 'skullduggery', permission: {mutable: true, collectionAdmin: false, tokenOwner: false}},305 {key: 'mindgame', permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},306 ]);307 }308309 itSub('Sets access rights to properties of a collection (NFT)', async ({helper}) => {310 await testSetsAccessRightsToProperties(await helper.nft.mintCollection(alice));311 });312313 itSub.ifWithPallets('Sets access rights to properties of a collection (ReFungible)', [Pallets.ReFungible], async ({helper}) => {314 await testSetsAccessRightsToProperties(await helper.rft.mintCollection(alice));315 });316 317 async function testChangesAccessRightsToProperty(collection: UniqueNFTCollection | UniqueRFTCollection) {318 await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: true, collectionAdmin: true}}]))319 .to.be.fulfilled;320321 await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]))322 .to.be.fulfilled;323324 const propertyRights = await collection.getPropertyPermissions();325 expect(propertyRights).to.be.deep.equal([326 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},327 ]);328 }329330 itSub('Changes access rights to properties of a NFT collection', async ({helper}) => {331 await testChangesAccessRightsToProperty(await helper.nft.mintCollection(alice));332 });333334 itSub.ifWithPallets('Changes access rights to properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {335 await testChangesAccessRightsToProperty(await helper.rft.mintCollection(alice));336 });337});338339describe('Negative Integration Test: Access Rights to Token Properties', () => {340 let alice: IKeyringPair;341 let bob: IKeyringPair;342343 before(async () => {344 await usingPlaygrounds(async (helper, privateKey) => {345 const donor = privateKey('//Alice');346 [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);347 });348 });349350 async function testPreventsFromSettingAccessRightsNotAdminOrOwner(collection: UniqueNFTCollection | UniqueRFTCollection) {351 await expect(collection.setTokenPropertyPermissions(bob, [{key: 'skullduggery', permission: {mutable: true, tokenOwner: true}}]))352 .to.be.rejectedWith(/common\.NoPermission/);353354 const propertyRights = await collection.getPropertyPermissions(['skullduggery']);355 expect(propertyRights).to.be.empty;356 }357358 itSub('Prevents from setting access rights to properties of a NFT collection if not an onwer/admin', async ({helper}) => {359 await testPreventsFromSettingAccessRightsNotAdminOrOwner(await helper.nft.mintCollection(alice));360 });361362 itSub.ifWithPallets('Prevents from setting access rights to properties of a ReFungible collection if not an onwer/admin', [Pallets.ReFungible], async ({helper}) => {363 await testPreventsFromSettingAccessRightsNotAdminOrOwner(await helper.rft.mintCollection(alice));364 });365366 async function testPreventFromAddingTooManyPossibleProperties(collection: UniqueNFTCollection | UniqueRFTCollection) { 367 const constitution = [];368 for (let i = 0; i < 65; i++) {369 constitution.push({370 key: 'property_' + i,371 permission: Math.random() > 0.5 ? {mutable: true, collectionAdmin: true, tokenOwner: true} : {},372 });373 }374375 await expect(collection.setTokenPropertyPermissions(alice, constitution))376 .to.be.rejectedWith(/common\.PropertyLimitReached/);377378 const propertyRights = await collection.getPropertyPermissions();379 expect(propertyRights).to.be.empty;380 }381382 itSub('Prevents from adding too many possible properties (NFT)', async ({helper}) => {383 await testPreventFromAddingTooManyPossibleProperties(await helper.nft.mintCollection(alice));384 });385386 itSub.ifWithPallets('Prevents from adding too many possible properties (ReFungible)', [Pallets.ReFungible], async ({helper}) => {387 await testPreventFromAddingTooManyPossibleProperties(await helper.rft.mintCollection(alice));388 });389390 async function testPreventAccessRightsModifiedIfConstant(collection: UniqueNFTCollection | UniqueRFTCollection) {391 await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]))392 .to.be.fulfilled;393394 await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {collectionAdmin: true}}]))395 .to.be.rejectedWith(/common\.NoPermission/);396397 const propertyRights = await collection.getPropertyPermissions(['skullduggery']);398 expect(propertyRights).to.deep.equal([399 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},400 ]);401 }402403 itSub('Prevents access rights to be modified if constant (NFT)', async ({helper}) => {404 await testPreventAccessRightsModifiedIfConstant(await helper.nft.mintCollection(alice));405 });406407 itSub.ifWithPallets('Prevents access rights to be modified if constant (ReFungible)', [Pallets.ReFungible], async ({helper}) => {408 await testPreventAccessRightsModifiedIfConstant(await helper.rft.mintCollection(alice));409 });410411 async function testPreventsAddingPropertiesWithInvalidNames(collection: UniqueNFTCollection | UniqueRFTCollection) {412 const invalidProperties = [413 [{key: 'skullduggery', permission: {tokenOwner: true}}, {key: 'im possible', permission: {collectionAdmin: true}}],414 [{key: 'G#4', permission: {tokenOwner: true}}],415 [{key: 'HÆMILTON', permission: {mutable: false, collectionAdmin: true, tokenOwner: true}}],416 ];417418 for (let i = 0; i < invalidProperties.length; i++) {419 await expect(420 collection.setTokenPropertyPermissions(alice, invalidProperties[i]), 421 `on setting the new badly-named property #${i}`,422 ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);423 }424425 await expect(426 collection.setTokenPropertyPermissions(alice, [{key: '', permission: {}}]), 427 'on rejecting an unnamed property',428 ).to.be.rejectedWith(/common\.EmptyPropertyKey/);429430 const correctKey = '--0x03116e387820CA05'; // PolkadotJS would parse this as an already encoded hex-string431 await expect(432 collection.setTokenPropertyPermissions(alice, [433 {key: correctKey, permission: {collectionAdmin: true}},434 ]), 435 'on setting the correctly-but-still-badly-named property',436 ).to.be.fulfilled;437438 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat(correctKey).concat('');439440 const propertyRights = await collection.getPropertyPermissions(keys);441 expect(propertyRights).to.be.deep.equal([442 {key: correctKey, permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},443 ]);444 }445446 itSub('Prevents adding properties with invalid names (NFT)', async ({helper}) => {447 await testPreventsAddingPropertiesWithInvalidNames(await helper.nft.mintCollection(alice));448 });449450 itSub.ifWithPallets('Prevents adding properties with invalid names (ReFungible)', [Pallets.ReFungible], async ({helper}) => {451 await testPreventsAddingPropertiesWithInvalidNames(await helper.rft.mintCollection(alice));452 });453});454455// ---------- TOKEN PROPERTIES456457describe('Integration Test: Token Properties', () => {458 let alice: IKeyringPair; // collection owner459 let bob: IKeyringPair; // collection admin460 let charlie: IKeyringPair; // token owner461462 let permissions: {permission: any, signers: IKeyringPair[]}[];463464 before(async () => {465 await usingPlaygrounds(async (helper, privateKey) => {466 const donor = privateKey('//Alice');467 [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);468 });469470 // todo:playgrounds probably separate these tests later471 permissions = [472 {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob]},473 {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob]},474 {permission: {mutable: true, tokenOwner: true}, signers: [charlie]},475 {permission: {mutable: false, tokenOwner: true}, signers: [charlie]},476 {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},477 {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},478 ];479 });480 481 async function testReadsYetEmptyProperties(token: UniqueNFTToken | UniqueRFTToken) {482 const properties = await token.getProperties();483 expect(properties).to.be.empty;484485 const tokenData = await token.getData();486 expect(tokenData!.properties).to.be.empty;487 }488489 itSub('Reads yet empty properties of a token (NFT)', async ({helper}) => {490 const collection = await helper.nft.mintCollection(alice);491 const token = await collection.mintToken(alice);492 await testReadsYetEmptyProperties(token);493 });494495 itSub.ifWithPallets('Reads yet empty properties of a token (ReFungible)', [Pallets.ReFungible], async ({helper}) => {496 const collection = await helper.rft.mintCollection(alice);497 const token = await collection.mintToken(alice);498 await testReadsYetEmptyProperties(token);499 });500501 async function testAssignPropertiesAccordingToPermissions(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) {502 await token.collection.addAdmin(alice, {Substrate: bob.address});503 await token.transfer(alice, {Substrate: charlie.address}, pieces);504505 const propertyKeys: string[] = [];506 let i = 0;507 for (const permission of permissions) {508 i++;509 let j = 0;510 for (const signer of permission.signers) {511 j++;512 const key = i + '_' + signer.address;513 propertyKeys.push(key);514515 await expect(516 token.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 517 `on setting permission #${i} by alice`,518 ).to.be.fulfilled;519520 await expect(521 token.setProperties(signer, [{key: key, value: 'Serotonin increase'}]), 522 `on adding property #${i} by signer #${j}`,523 ).to.be.fulfilled;524 }525 }526527 const properties = await token.getProperties(propertyKeys);528 const tokenData = await token.getData();529 for (let i = 0; i < properties.length; i++) {530 expect(properties[i].value).to.be.equal('Serotonin increase');531 expect(tokenData!.properties[i].value).to.be.equal('Serotonin increase');532 }533 }534535 itSub('Assigns properties to a token according to permissions (NFT)', async ({helper}) => {536 const collection = await helper.nft.mintCollection(alice);537 const token = await collection.mintToken(alice);538 await testAssignPropertiesAccordingToPermissions(token, 1n);539 });540541 itSub.ifWithPallets('Assigns properties to a token according to permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {542 const collection = await helper.rft.mintCollection(alice);543 const token = await collection.mintToken(alice, 100n);544 await testAssignPropertiesAccordingToPermissions(token, 100n);545 });546547 async function testChangesPropertiesAccordingPermission(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) {548 await token.collection.addAdmin(alice, {Substrate: bob.address});549 await token.transfer(alice, {Substrate: charlie.address}, pieces);550551 const propertyKeys: string[] = [];552 let i = 0;553 for (const permission of permissions) {554 i++;555 if (!permission.permission.mutable) continue;556 557 let j = 0;558 for (const signer of permission.signers) {559 j++;560 const key = i + '_' + signer.address;561 propertyKeys.push(key);562563 await expect(564 token.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 565 `on setting permission #${i} by alice`,566 ).to.be.fulfilled;567568 await expect(569 token.setProperties(signer, [{key, value: 'Serotonin increase'}]), 570 `on adding property #${i} by signer #${j}`,571 ).to.be.fulfilled;572573 await expect(574 token.setProperties(signer, [{key, value: 'Serotonin stable'}]), 575 `on changing property #${i} by signer #${j}`,576 ).to.be.fulfilled;577 }578 }579580 const properties = await token.getProperties(propertyKeys);581 const tokenData = await token.getData();582 for (let i = 0; i < properties.length; i++) {583 expect(properties[i].value).to.be.equal('Serotonin stable');584 expect(tokenData!.properties[i].value).to.be.equal('Serotonin stable');585 }586 }587588 itSub('Changes properties of a token according to permissions (NFT)', async ({helper}) => {589 const collection = await helper.nft.mintCollection(alice);590 const token = await collection.mintToken(alice);591 await testChangesPropertiesAccordingPermission(token, 1n);592 });593594 itSub.ifWithPallets('Changes properties of a token according to permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {595 const collection = await helper.rft.mintCollection(alice);596 const token = await collection.mintToken(alice, 100n);597 await testChangesPropertiesAccordingPermission(token, 100n);598 });599600 async function testDeletePropertiesAccordingPermission(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) {601 await token.collection.addAdmin(alice, {Substrate: bob.address});602 await token.transfer(alice, {Substrate: charlie.address}, pieces);603604 const propertyKeys: string[] = [];605 let i = 0;606607 for (const permission of permissions) {608 i++;609 if (!permission.permission.mutable) continue;610 611 let j = 0;612 for (const signer of permission.signers) {613 j++;614 const key = i + '_' + signer.address;615 propertyKeys.push(key);616617 await expect(618 token.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 619 `on setting permission #${i} by alice`,620 ).to.be.fulfilled;621622 await expect(623 token.setProperties(signer, [{key, value: 'Serotonin increase'}]), 624 `on adding property #${i} by signer #${j}`,625 ).to.be.fulfilled;626627 await expect(628 token.deleteProperties(signer, [key]), 629 `on deleting property #${i} by signer #${j}`,630 ).to.be.fulfilled;631 }632 }633634 expect(await token.getProperties(propertyKeys)).to.be.empty;635 expect((await token.getData())!.properties).to.be.empty;636 }637 638 itSub('Deletes properties of a token according to permissions (NFT)', async ({helper}) => {639 const collection = await helper.nft.mintCollection(alice);640 const token = await collection.mintToken(alice);641 await testDeletePropertiesAccordingPermission(token, 1n);642 });643644 itSub.ifWithPallets('Deletes properties of a token according to permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {645 const collection = await helper.rft.mintCollection(alice);646 const token = await collection.mintToken(alice, 100n);647 await testDeletePropertiesAccordingPermission(token, 100n);648 });649650 itSub('Assigns properties to a nested token according to permissions', async ({helper}) => {651 const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});652 const collectionB = await helper.nft.mintCollection(alice);653 const targetToken = await collectionA.mintToken(alice);654 const nestedToken = await collectionB.mintToken(alice, targetToken.nestingAccount());655656 await collectionB.addAdmin(alice, {Substrate: bob.address});657 await targetToken.transfer(alice, {Substrate: charlie.address});658659 const propertyKeys: string[] = [];660 let i = 0;661 for (const permission of permissions) {662 i++;663 let j = 0;664 for (const signer of permission.signers) {665 j++;666 const key = i + '_' + signer.address;667 propertyKeys.push(key);668 669 await expect(670 nestedToken.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 671 `on setting permission #${i} by alice`,672 ).to.be.fulfilled;673674 await expect(675 nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), 676 `on adding property #${i} by signer #${j}`,677 ).to.be.fulfilled;678 }679680 }681682 const properties = await nestedToken.getProperties(propertyKeys);683 const tokenData = await nestedToken.getData();684 for (let i = 0; i < properties.length; i++) {685 expect(properties[i].value).to.be.equal('Serotonin increase');686 expect(tokenData!.properties[i].value).to.be.equal('Serotonin increase');687 }688 expect(await targetToken.getProperties()).to.be.empty;689 });690691 itSub('Changes properties of a nested token according to permissions', async ({helper}) => {692 const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});693 const collectionB = await helper.nft.mintCollection(alice);694 const targetToken = await collectionA.mintToken(alice);695 const nestedToken = await collectionB.mintToken(alice, targetToken.nestingAccount());696697 await collectionB.addAdmin(alice, {Substrate: bob.address});698 await targetToken.transfer(alice, {Substrate: charlie.address});699700 const propertyKeys: string[] = [];701 let i = 0;702 for (const permission of permissions) {703 i++;704 if (!permission.permission.mutable) continue;705 706 let j = 0;707 for (const signer of permission.signers) {708 j++;709 const key = i + '_' + signer.address;710 propertyKeys.push(key);711712 await expect(713 nestedToken.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 714 `on setting permission #${i} by alice`,715 ).to.be.fulfilled;716717 await expect(718 nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), 719 `on adding property #${i} by signer #${j}`,720 ).to.be.fulfilled;721722 await expect(723 nestedToken.setProperties(signer, [{key, value: 'Serotonin stable'}]), 724 `on changing property #${i} by signer #${j}`,725 ).to.be.fulfilled;726 }727 }728729 const properties = await nestedToken.getProperties(propertyKeys);730 const tokenData = await nestedToken.getData();731 for (let i = 0; i < properties.length; i++) {732 expect(properties[i].value).to.be.equal('Serotonin stable');733 expect(tokenData!.properties[i].value).to.be.equal('Serotonin stable');734 }735 expect(await targetToken.getProperties()).to.be.empty;736 });737738 itSub('Deletes properties of a nested token according to permissions', async ({helper}) => {739 const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});740 const collectionB = await helper.nft.mintCollection(alice);741 const targetToken = await collectionA.mintToken(alice);742 const nestedToken = await collectionB.mintToken(alice, targetToken.nestingAccount());743744 await collectionB.addAdmin(alice, {Substrate: bob.address});745 await targetToken.transfer(alice, {Substrate: charlie.address});746747 const propertyKeys: string[] = [];748 let i = 0;749 for (const permission of permissions) {750 i++;751 if (!permission.permission.mutable) continue;752 753 let j = 0;754 for (const signer of permission.signers) {755 j++;756 const key = i + '_' + signer.address;757 propertyKeys.push(key);758759 await expect(760 nestedToken.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 761 `on setting permission #${i} by alice`,762 ).to.be.fulfilled;763764 await expect(765 nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), 766 `on adding property #${i} by signer #${j}`,767 ).to.be.fulfilled;768769 await expect(770 nestedToken.deleteProperties(signer, [key]), 771 `on deleting property #${i} by signer #${j}`,772 ).to.be.fulfilled;773 }774 }775776 expect(await nestedToken.getProperties(propertyKeys)).to.be.empty;777 expect((await nestedToken.getData())!.properties).to.be.empty;778 expect(await targetToken.getProperties()).to.be.empty;779 });780});781782describe('Negative Integration Test: Token Properties', () => {783 let alice: IKeyringPair; // collection owner784 let bob: IKeyringPair; // collection admin785 let charlie: IKeyringPair; // token owner786787 let constitution: {permission: any, signers: IKeyringPair[], sinner: IKeyringPair}[];788789 before(async () => {790 await usingPlaygrounds(async (helper, privateKey) => {791 const donor = privateKey('//Alice');792 let dave: IKeyringPair;793 [alice, bob, charlie, dave] = await helper.arrange.createAccounts([100n, 100n, 100n, 100n], donor);794795 // todo:playgrounds probably separate these tests later796 constitution = [797 {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},798 {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},799 {permission: {mutable: true, tokenOwner: true}, signers: [charlie], sinner: alice},800 {permission: {mutable: false, tokenOwner: true}, signers: [charlie], sinner: alice},801 {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie], sinner: dave},802 {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie], sinner: dave},803 ];804 });805 });806807 async function getConsumedSpace(api: any, collectionId: number, tokenId: number, mode: 'NFT' | 'RFT'): Promise<number> {808 return (await (mode == 'NFT' ? api.query.nonfungible : api.query.refungible).tokenProperties(collectionId, tokenId)).toJSON().consumedSpace;809 }810811 async function prepare(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint): Promise<number> {812 await token.collection.addAdmin(alice, {Substrate: bob.address});813 await token.transfer(alice, {Substrate: charlie.address}, pieces);814815 let i = 0;816 for (const passage of constitution) {817 i++;818 const signer = passage.signers[0];819 820 await expect(821 token.collection.setTokenPropertyPermissions(alice, [{key: `${i}`, permission: passage.permission}]), 822 `on setting permission ${i} by alice`,823 ).to.be.fulfilled;824825 await expect(826 token.setProperties(signer, [{key: `${i}`, value: 'Serotonin increase'}]), 827 `on adding property ${i} by ${signer.address}`,828 ).to.be.fulfilled;829 }830831 const originalSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 832 return originalSpace;833 }834835 async function testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) {836 const originalSpace = await prepare(token, pieces);837838 let i = 0;839 for (const forbiddance of constitution) {840 i++;841 if (!forbiddance.permission.mutable) continue;842843 await expect(844 token.setProperties(forbiddance.sinner, [{key: `${i}`, value: 'Serotonin down'}]), 845 `on failing to change property ${i} by the malefactor`,846 ).to.be.rejectedWith(/common\.NoPermission/);847848 await expect(849 token.deleteProperties(forbiddance.sinner, [`${i}`]), 850 `on failing to delete property ${i} by the malefactor`,851 ).to.be.rejectedWith(/common\.NoPermission/);852 }853854 const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 855 expect(consumedSpace).to.be.equal(originalSpace);856 }857858 itSub('Forbids changing/deleting properties of a token if the user is outside of permissions (NFT)', async ({helper}) => {859 const collection = await helper.nft.mintCollection(alice);860 const token = await collection.mintToken(alice);861 await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token, 1n);862 });863864 itSub.ifWithPallets('Forbids changing/deleting properties of a token if the user is outside of permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {865 const collection = await helper.rft.mintCollection(alice);866 const token = await collection.mintToken(alice, 100n);867 await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token, 100n);868 });869870 async function testForbidsChangingDeletingPropertiesIfPropertyImmutable(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) {871 const originalSpace = await prepare(token, pieces);872873 let i = 0;874 for (const permission of constitution) {875 i++;876 if (permission.permission.mutable) continue;877878 await expect(879 token.setProperties(permission.signers[0], [{key: `${i}`, value: 'Serotonin down'}]), 880 `on failing to change property ${i} by signer #0`,881 ).to.be.rejectedWith(/common\.NoPermission/);882883 await expect(884 token.deleteProperties(permission.signers[0], [i.toString()]), 885 `on failing to delete property ${i} by signer #0`,886 ).to.be.rejectedWith(/common\.NoPermission/);887 }888 889 const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 890 expect(consumedSpace).to.be.equal(originalSpace);891 }892893 itSub('Forbids changing/deleting properties of a token if the property is permanent (immutable) (NFT)', async ({helper}) => {894 const collection = await helper.nft.mintCollection(alice);895 const token = await collection.mintToken(alice);896 await testForbidsChangingDeletingPropertiesIfPropertyImmutable(token, 1n);897 });898899 itSub.ifWithPallets('Forbids changing/deleting properties of a token if the property is permanent (immutable) (ReFungible)', [Pallets.ReFungible], async ({helper}) => {900 const collection = await helper.rft.mintCollection(alice);901 const token = await collection.mintToken(alice, 100n);902 await testForbidsChangingDeletingPropertiesIfPropertyImmutable(token, 100n);903 });904905 async function testForbidsAddingPropertiesIfPropertyNotDeclared(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) {906 const originalSpace = await prepare(token, pieces);907908 await expect(909 token.setProperties(alice, [{key: 'non-existent', value: 'I exist!'}]), 910 'on failing to add a previously non-existent property',911 ).to.be.rejectedWith(/common\.NoPermission/);912 913 await expect(914 token.collection.setTokenPropertyPermissions(alice, [{key: 'now-existent', permission: {}}]), 915 'on setting a new non-permitted property',916 ).to.be.fulfilled;917918 await expect(919 token.setProperties(alice, [{key: 'now-existent', value: 'I exist!'}]), 920 'on failing to add a property forbidden by the \'None\' permission',921 ).to.be.rejectedWith(/common\.NoPermission/);922923 expect(await token.getProperties(['non-existent', 'now-existent'])).to.be.empty;924 925 const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 926 expect(consumedSpace).to.be.equal(originalSpace);927 }928929 itSub('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (NFT)', async ({helper}) => {930 const collection = await helper.nft.mintCollection(alice);931 const token = await collection.mintToken(alice);932 await testForbidsAddingPropertiesIfPropertyNotDeclared(token, 1n);933 });934935 itSub.ifWithPallets('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (ReFungible)', [Pallets.ReFungible], async ({helper}) => {936 const collection = await helper.rft.mintCollection(alice);937 const token = await collection.mintToken(alice, 100n);938 await testForbidsAddingPropertiesIfPropertyNotDeclared(token, 100n);939 });940941 async function testForbidsAddingTooManyProperties(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) {942 const originalSpace = await prepare(token, pieces);943944 await expect(945 token.collection.setTokenPropertyPermissions(alice, [946 {key: 'a_holy_book', permission: {collectionAdmin: true, tokenOwner: true}}, 947 {key: 'young_years', permission: {collectionAdmin: true, tokenOwner: true}},948 ]), 949 'on setting new permissions for properties',950 ).to.be.fulfilled;951952 // Mute the general tx parsing error953 {954 console.error = () => {};955 await expect(token.setProperties(alice, [{key: 'a_holy_book', value: 'word '.repeat(6554)}]))956 .to.be.rejected;957 }958959 await expect(token.setProperties(alice, [960 {key: 'a_holy_book', value: 'word '.repeat(3277)}, 961 {key: 'young_years', value: 'neverending'.repeat(1490)},962 ])).to.be.rejectedWith(/common\.NoSpaceForProperty/);963 964 expect(await token.getProperties(['a_holy_book', 'young_years'])).to.be.empty;965 const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 966 expect(consumedSpace).to.be.equal(originalSpace);967 }968969 itSub('Forbids adding too many properties to a token (NFT)', async ({helper}) => {970 const collection = await helper.nft.mintCollection(alice);971 const token = await collection.mintToken(alice);972 await testForbidsAddingTooManyProperties(token, 1n);973 });974975 itSub.ifWithPallets('Forbids adding too many properties to a token (ReFungible)', [Pallets.ReFungible], async ({helper}) => {976 const collection = await helper.rft.mintCollection(alice);977 const token = await collection.mintToken(alice, 100n);978 await testForbidsAddingTooManyProperties(token, 100n);979 });980});981982describe('ReFungible token properties permissions tests', () => {983 let alice: IKeyringPair;984 let bob: IKeyringPair;985 let charlie: IKeyringPair;986987 before(async function() {988 await usingPlaygrounds(async (helper, privateKey) => {989 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);990991 const donor = privateKey('//Alice');992 [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);993 });994 });995996 async function prepare(helper: UniqueHelper): Promise<UniqueRFTToken> {997 const collection = await helper.rft.mintCollection(alice);998 const token = await collection.mintToken(alice, 100n);999 1000 await collection.addAdmin(alice, {Substrate: bob.address});1001 await collection.setTokenPropertyPermissions(alice, [{key: 'fractals', permission: {mutable: true, tokenOwner: true}}]);1002 1003 return token;1004 }10051006 itSub('Forbids adding token property with tokenOwner==true when signer doesn\'t have all pieces', async ({helper}) => {1007 const token = await prepare(helper);10081009 await token.transfer(alice, {Substrate: charlie.address}, 33n);10101011 await expect(token.setProperties(alice, [1012 {key: 'fractals', value: 'multiverse'}, 1013 ])).to.be.rejectedWith(/common\.NoPermission/);1014 });10151016 itSub('Forbids mutating token property with tokenOwher==true when signer doesn\'t have all pieces', async ({helper}) => {1017 const token = await prepare(helper);10181019 await expect(token.collection.setTokenPropertyPermissions(alice, [{key: 'fractals', permission: {mutable:true, tokenOwner: true}}]))1020 .to.be.fulfilled;10211022 await expect(token.setProperties(alice, [1023 {key: 'fractals', value: 'multiverse'}, 1024 ])).to.be.fulfilled;10251026 await token.transfer(alice, {Substrate: charlie.address}, 33n);10271028 await expect(token.setProperties(alice, [1029 {key: 'fractals', value: 'want to rule the world'}, 1030 ])).to.be.rejectedWith(/common\.NoPermission/);1031 });10321033 itSub('Forbids deleting token property with tokenOwner==true when signer doesn\'t have all pieces', async ({helper}) => {1034 const token = await prepare(helper);10351036 await expect(token.setProperties(alice, [1037 {key: 'fractals', value: 'one headline - why believe it'}, 1038 ])).to.be.fulfilled;10391040 await token.transfer(alice, {Substrate: charlie.address}, 33n);10411042 await expect(token.deleteProperties(alice, ['fractals'])).1043 to.be.rejectedWith(/common\.NoPermission/);1044 });10451046 itSub('Allows token property mutation with collectionOwner==true when admin doesn\'t have all pieces', async ({helper}) => {1047 const token = await prepare(helper);10481049 await token.transfer(alice, {Substrate: charlie.address}, 33n);10501051 await expect(token.collection.setTokenPropertyPermissions(alice, [{key: 'fractals', permission: {mutable:true, collectionAdmin: true}}]))1052 .to.be.fulfilled;10531054 await expect(token.setProperties(alice, [1055 {key: 'fractals', value: 'multiverse'}, 1056 ])).to.be.fulfilled;1057 });1058});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 {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});tests/src/nesting/unnest.test.tsdiffbeforeafterboth--- a/tests/src/nesting/unnest.test.ts
+++ b/tests/src/nesting/unnest.test.ts
@@ -105,11 +105,11 @@
// Try to unnest
await expect(nestedToken.unnest(bob, targetToken, {Substrate: alice.address})).to.be.rejectedWith(/common\.ApprovedValueTooLow/);
- expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount());
+ expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());
// Try to burn
await expect(nestedToken.burnFrom(bob, targetToken.nestingAccount())).to.be.rejectedWith(/common\.ApprovedValueTooLow/);
- expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount());
+ expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());
});
// todo another test for creating excessive depth matryoshka with Ethereum?
tests/src/util/playgrounds/index.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/index.ts
+++ b/tests/src/util/playgrounds/index.ts
@@ -9,10 +9,16 @@
import '../../interfaces/augment-api-events';
import {DevUniqueHelper, SilentLogger, SilentConsole} from './unique.dev';
-
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,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import {IKeyringPair} from '@polkadot/types/types';
+import {UniqueHelper} from './unique';
export interface IEvent {
section: string;
@@ -106,7 +107,7 @@
}
}
-export interface IToken {
+export interface ITokenAddress {
collectionId: number;
tokenId: number;
}
@@ -167,3 +168,169 @@
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, IToken, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, TUniqueNetworks} from './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';
export const crossAccountIdFromLower = (lowerAddress: ICrossAccountIdLower): ICrossAccountId => {
const address = {} as ICrossAccountId;
@@ -55,11 +55,15 @@
RPC: 'rpc',
};
- static getTokenAccount(token: IToken) {
+ static getTokenAccount(token: ITokenAddress): ICrossAccountId {
+ return {Ethereum: this.getTokenAddress(token)};
+ }
+
+ static getTokenAccountInLowerCase(token: ITokenAddress): ICrossAccountId {
return {Ethereum: this.getTokenAddress(token).toLowerCase()};
}
- static getTokenAddress(token: IToken) {
+ static getTokenAddress(token: ITokenAddress): string {
return nesting.tokenIdToAddress(token.collectionId, token.tokenId);
}
@@ -1348,7 +1352,7 @@
* @example getTokenChildren(10, 5);
* @returns tokens whose depth of nesting is <= 5
*/
- async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise<IToken[]> {
+ async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise<ITokenAddress[]> {
let children;
if(typeof blockHashAt === 'undefined') {
children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId]);
@@ -1369,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: IToken, rootTokenObj: IToken): Promise<boolean> {
+ async nestToken(signer: TSigner, tokenObj: ITokenAddress, rootTokenObj: ITokenAddress): Promise<boolean> {
const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);
const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);
if(!result) {
@@ -1387,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: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId): Promise<boolean> {
+ async unnestToken(signer: TSigner, tokenObj: ITokenAddress, rootTokenObj: ITokenAddress, 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) {
@@ -1456,7 +1460,7 @@
true,
);
const collection = this.getCollectionObject(collectionId);
- return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));
+ return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: ITokenAddress) => collection.getTokenObject(x.tokenId));
}
/**
@@ -1489,7 +1493,7 @@
true,
);
const collection = this.getCollectionObject(collectionId);
- return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));
+ return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: ITokenAddress) => collection.getTokenObject(x.tokenId));
}
/**
@@ -1630,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: IToken) => collection.getTokenObject(x.tokenId));
+ return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: ITokenAddress) => collection.getTokenObject(x.tokenId));
}
/**
@@ -1654,7 +1658,7 @@
true,
);
const collection = this.getCollectionObject(collectionId);
- return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));
+ return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: ITokenAddress) => collection.getTokenObject(x.tokenId));
}
/**
@@ -2219,7 +2223,7 @@
}
-export class UniqueCollectionBase {
+class UniqueCollectionBase implements ICollectionBase {
helper: UniqueHelper;
collectionId: number;
@@ -2256,6 +2260,10 @@
return await this.helper.collection.getProperties(this.collectionId, propertyKeys);
}
+ async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {
+ return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);
+ }
+
async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount) {
return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress);
}
@@ -2300,10 +2308,6 @@
return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys);
}
- async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {
- return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);
- }
-
async setPermissions(signer: TSigner, permissions: ICollectionPermissions) {
return await this.helper.collection.setPermissions(signer, this.collectionId, permissions);
}
@@ -2322,7 +2326,7 @@
}
-export class UniqueNFTCollection extends UniqueCollectionBase {
+class UniqueNFTCollection extends UniqueCollectionBase implements ICollectionNFT {
getTokenObject(tokenId: number) {
return new UniqueNFTToken(tokenId, this);
}
@@ -2399,17 +2403,17 @@
return await this.helper.nft.setTokenPropertyPermissions(signer, this.collectionId, permissions);
}
- async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken) {
+ async nestToken(signer: TSigner, tokenId: number, toTokenObj: ITokenAddress) {
return await this.helper.nft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj);
}
- async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {
+ async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: ITokenAddress, toAddressObj: ICrossAccountId) {
return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);
}
}
-export class UniqueRFTCollection extends UniqueCollectionBase {
+class UniqueRFTCollection extends UniqueCollectionBase implements ICollectionRFT {
getTokenObject(tokenId: number) {
return new UniqueRFTToken(tokenId, this);
}
@@ -2434,6 +2438,10 @@
return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId);
}
+ async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {
+ return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);
+ }
+
async getPropertyPermissions(propertyKeys: string[] | null = null) {
return await this.helper.rft.getPropertyPermissions(this.collectionId, propertyKeys);
}
@@ -2452,10 +2460,6 @@
async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) {
return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, amount);
- }
-
- async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {
- return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);
}
async repartitionToken(signer: TSigner, tokenId: number, amount: bigint) {
@@ -2492,23 +2496,31 @@
}
-export class UniqueFTCollection extends UniqueCollectionBase {
- async mint(signer: TSigner, amount=1n, owner: ICrossAccountId = {Substrate: signer.address}) {
- return await this.helper.ft.mintTokens(signer, this.collectionId, amount, owner);
+class UniqueFTCollection extends UniqueCollectionBase implements ICollectionFT {
+ async getBalance(addressObj: ICrossAccountId) {
+ return await this.helper.ft.getBalance(this.collectionId, addressObj);
}
- async mintWithOneOwner(signer: TSigner, tokens: {value: bigint}[], owner: ICrossAccountId = {Substrate: signer.address}) {
- return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, tokens, owner);
+ async getTotalPieces() {
+ return await this.helper.ft.getTotalPieces(this.collectionId);
}
- async getBalance(addressObj: ICrossAccountId) {
- return await this.helper.ft.getBalance(this.collectionId, addressObj);
+ async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {
+ return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj);
}
async getTop10Owners() {
return await this.helper.ft.getTop10Owners(this.collectionId);
}
+ async mint(signer: TSigner, amount=1n, owner: ICrossAccountId = {Substrate: signer.address}) {
+ return await this.helper.ft.mintTokens(signer, this.collectionId, amount, owner);
+ }
+
+ async mintWithOneOwner(signer: TSigner, tokens: {value: bigint}[], owner: ICrossAccountId = {Substrate: signer.address}) {
+ return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, tokens, owner);
+ }
+
async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {
return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount);
}
@@ -2523,23 +2535,15 @@
async burnTokensFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount=1n) {
return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount);
- }
-
- async getTotalPieces() {
- return await this.helper.ft.getTotalPieces(this.collectionId);
}
async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {
return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount);
}
-
- async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {
- return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj);
- }
}
-export class UniqueTokenBase implements IToken {
+class UniqueTokenBase implements ITokenBase {
collection: UniqueNFTCollection | UniqueRFTCollection;
collectionId: number;
tokenId: number;
@@ -2569,10 +2573,14 @@
nestingAccount() {
return this.collection.helper.util.getTokenAccount(this);
}
+
+ nestingAccountInLowerCase() {
+ return this.collection.helper.util.getTokenAccountInLowerCase(this);
+ }
}
-export class UniqueNFTToken extends UniqueTokenBase {
+class UniqueNFTToken extends UniqueTokenBase implements ITokenNonfungible {
collection: UniqueNFTCollection;
constructor(tokenId: number, collection: UniqueNFTCollection) {
@@ -2596,11 +2604,11 @@
return await this.collection.getTokenChildren(this.tokenId, blockHashAt);
}
- async nest(signer: TSigner, toTokenObj: IToken) {
+ async nest(signer: TSigner, toTokenObj: ITokenAddress) {
return await this.collection.nestToken(signer, this.tokenId, toTokenObj);
}
- async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {
+ async unnest(signer: TSigner, fromTokenObj: ITokenAddress, toAddressObj: ICrossAccountId) {
return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj);
}
@@ -2629,7 +2637,7 @@
}
}
-export class UniqueRFTToken extends UniqueTokenBase {
+class UniqueRFTToken extends UniqueTokenBase implements ITokenRefungible {
collection: UniqueRFTCollection;
constructor(tokenId: number, collection: UniqueRFTCollection) {
@@ -2653,6 +2661,10 @@
return await this.collection.getTokenTotalPieces(this.tokenId);
}
+ async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {
+ return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);
+ }
+
async transfer(signer: TSigner, addressObj: ICrossAccountId, amount=1n) {
return await this.collection.transferToken(signer, this.tokenId, addressObj, amount);
}
@@ -2663,10 +2675,6 @@
async approve(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {
return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount);
- }
-
- async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {
- return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);
}
async repartition(signer: TSigner, amount: bigint) {