difftreelog
fix tests around foreign flag
in: master
3 files changed
js-packages/playgrounds/types.tsdiffbeforeafterboth--- a/js-packages/playgrounds/types.ts
+++ b/js-packages/playgrounds/types.ts
@@ -160,8 +160,6 @@
External = 1,
/// Supports ERC721Metadata
Erc721metadata = 64,
- /// Tokens in foreign collections can be transferred, but not burnt
- Foreign = 128,
}
export interface ICollectionCreationOptions {
js-packages/tests/createCollection.test.tsdiffbeforeafterboth--- a/js-packages/tests/createCollection.test.ts
+++ b/js-packages/tests/createCollection.test.ts
@@ -106,18 +106,6 @@
name: 'name', description: 'descr', tokenPrefix: 'COL',
flags: [CollectionFlag.Erc721metadata],
}, 'nft');
-
- // User can not set Foreign flag itself
-
- await expect(mintCollectionHelper(helper, alice, {
- name: 'name', description: 'descr', tokenPrefix: 'COL',
- flags: [CollectionFlag.Foreign],
- }, 'nft')).to.be.rejectedWith(/common.NoPermission/);
-
- await expect(mintCollectionHelper(helper, alice, {
- name: 'name', description: 'descr', tokenPrefix: 'COL',
- flags: [CollectionFlag.Erc721metadata, CollectionFlag.Foreign],
- }, 'nft')).to.be.rejectedWith(/common.NoPermission/);
});
itSub('Create new collection with extra fields', async ({helper}) => {
js-packages/tests/eth/createCollection.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.8//9// 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 type {IKeyringPair} from '@polkadot/types/types';18import {evmToAddress} from '@polkadot/util-crypto';19import {Pallets, requirePalletsOrSkip} from '../util/index.js';20import {expect, itEth, usingEthPlaygrounds} from './util/index.js';21import {CREATE_COLLECTION_DATA_DEFAULTS, CollectionLimitField, CollectionMode, CreateCollectionData, TokenPermissionField, emptyAddress} from './util/playgrounds/types.js';22import {CollectionFlag} from '@unique/playgrounds/types.js';23import type {IEthCrossAccountId, TCollectionMode} from '@unique/playgrounds/types.js';2425const DECIMALS = 18;26const CREATE_COLLECTION_DATA_DEFAULTS_ARRAY = [27 [], // properties28 [], // tokenPropertyPermissions29 [], // adminList30 [false, false, []], // nestingSettings31 [], // limits32 emptyAddress, // pendingSponsor33 [0], // flags34];3536type ElementOf<A> = A extends readonly (infer T)[] ? T : never;37function* cartesian<T extends Array<Array<any>>, R extends Array<any>>(internalRest: [...R], ...args: [...T]): Generator<[...R, ...{[K in keyof T]: ElementOf<T[K]>}]> {38 if(args.length === 0) {39 yield internalRest as any;40 return;41 }42 for(const value of args[0]) {43 yield* cartesian([...internalRest, value], ...args.slice(1)) as any;44 }45}4647describe('Create collection from EVM', () => {48 let donor: IKeyringPair;49 let nominal: bigint;5051 before(async function() {52 await usingEthPlaygrounds(async (helper, privateKey) => {53 donor = await privateKey({url: import.meta.url});54 nominal = helper.balance.getOneTokenNominal();55 });56 });5758 describe('Fungible collection', () => {59 before(async function() {60 await usingEthPlaygrounds((helper) => {61 requirePalletsOrSkip(this, helper, [Pallets.Fungible]);62 return Promise.resolve();63 });64 });6566 itEth('Collection address exist', async ({helper}) => {67 const owner = await helper.eth.createAccountWithBalance(donor);68 const collectionAddressForNonexistentCollection = '0x17C4E6453CC49AAAAEACA894E6D9683E00112233';69 const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);7071 expect(await collectionHelpers72 .methods.isCollectionExist(collectionAddressForNonexistentCollection).call())73 .to.be.false;7475 const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Exister', 'absolutely anything', 'WIWT', 'ft', DECIMALS)).send();76 expect(await collectionHelpers77 .methods.isCollectionExist(collectionAddress).call())78 .to.be.true;7980 // check collectionOwner:81 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true);82 const collectionOwner = await collectionEvm.methods.collectionOwner().call();83 expect(helper.address.restoreCrossAccountFromBigInt(BigInt(collectionOwner.sub))).to.eq(helper.address.ethToSubstrate(owner, true));84 });8586 itEth('destroyCollection', async ({helper}) => {87 const owner = await helper.eth.createAccountWithBalance(donor);88 const {collectionAddress, collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData('Exister', 'absolutely anything', 'WIWT', 'ft', DECIMALS)).send();89 const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);9091 const result = await collectionHelper.methods92 .destroyCollection(collectionAddress)93 .send({from: owner});9495 const events = helper.eth.normalizeEvents(result.events);9697 expect(events).to.be.deep.equal([98 {99 address: collectionHelper.options.address,100 event: 'CollectionDestroyed',101 args: {102 collectionId: collectionAddress,103 },104 },105 ]);106107 expect(await collectionHelper.methods108 .isCollectionExist(collectionAddress)109 .call()).to.be.false;110 expect(await helper.collection.getData(collectionId)).to.be.null;111 });112113 itEth('(!negative test!) Create collection (bad lengths)', async ({helper}) => {114 const owner = await helper.eth.createAccountWithBalance(donor);115 const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);116 {117 const MAX_NAME_LENGTH = 64;118 const collectionName = 'A'.repeat(MAX_NAME_LENGTH + 1);119 const description = 'A';120 const tokenPrefix = 'A';121122 await expect(collectionHelper.methods123 .createCollection([124 collectionName,125 description,126 tokenPrefix,127 CollectionMode.Fungible,128 DECIMALS,129 ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,130 ])131 .call({value: Number(2n * nominal)})).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGTH);132 }133 {134 const MAX_DESCRIPTION_LENGTH = 256;135 const collectionName = 'A';136 const description = 'A'.repeat(MAX_DESCRIPTION_LENGTH + 1);137 const tokenPrefix = 'A';138 await expect(collectionHelper.methods139 .createCollection([140 collectionName,141 description,142 tokenPrefix,143 CollectionMode.Fungible,144 DECIMALS,145 ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,146 ])147 .call({value: Number(2n * nominal)})).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGTH);148 }149 {150 const MAX_TOKEN_PREFIX_LENGTH = 16;151 const collectionName = 'A';152 const description = 'A';153 const tokenPrefix = 'A'.repeat(MAX_TOKEN_PREFIX_LENGTH + 1);154 await expect(collectionHelper.methods155 .createCollection([156 collectionName,157 description,158 tokenPrefix,159 CollectionMode.Fungible,160 DECIMALS,161 ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,162 ])163 .call({value: Number(2n * nominal)})).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGTH);164 }165 });166167 itEth('(!negative test!) cannot create collection if value !== 2', async ({helper}) => {168 const owner = await helper.eth.createAccountWithBalance(donor);169 const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);170 const expects = [0n, 1n, 30n].map(async value => {171 await expect(collectionHelper.methods172 .createCollection([173 'Peasantry',174 'absolutely anything',175 'TWIW',176 CollectionMode.Fungible,177 DECIMALS,178 ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,179 ])180 .call({value: Number(value * nominal)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');181 });182 await Promise.all(expects);183 });184 });185186 describe('Nonfungible collection', () => {187 before(async function() {188 await usingEthPlaygrounds((helper) => {189 requirePalletsOrSkip(this, helper, [Pallets.NFT]);190 return Promise.resolve();191 });192 });193194 itEth('Create collection', async ({helper}) => {195 const owner = await helper.eth.createAccountWithBalance(donor);196197 const name = 'CollectionEVM';198 const description = 'Some description';199 const prefix = 'token prefix';200201 // todo:playgrounds this might fail when in async environment.202 const collectionCountBefore = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;203 const {collectionId, collectionAddress, events} = await helper.eth.createCollection(owner, new CreateCollectionData(name, description, prefix, 'nft')).send();204205 expect(events).to.be.deep.equal([206 {207 address: '0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F',208 event: 'CollectionCreated',209 args: {210 owner: owner,211 collectionId: collectionAddress,212 },213 },214 ]);215216 const collectionCountAfter = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;217218 const collection = helper.nft.getCollectionObject(collectionId);219 const data = (await collection.getData())!;220221 // Parallel test safety222 expect(collectionCountAfter - collectionCountBefore).to.be.gte(1);223 expect(collectionId).to.be.eq(collectionCountAfter);224 expect(data.name).to.be.eq(name);225 expect(data.description).to.be.eq(description);226 expect(data.raw.tokenPrefix).to.be.eq(prefix);227 expect(data.raw.mode).to.be.eq('NFT');228229 const options = await collection.getOptions();230231 expect(options.tokenPropertyPermissions).to.be.empty;232 });233234 // this test will occasionally fail when in async environment.235 itEth('Check collection address exist', async ({helper}) => {236 const owner = await helper.eth.createAccountWithBalance(donor);237238 const expectedCollectionId = +(await helper.callRpc('api.rpc.unique.collectionStats')).created + 1;239 const expectedCollectionAddress = helper.ethAddress.fromCollectionId(expectedCollectionId);240 const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);241242 expect(await collectionHelpers.methods243 .isCollectionExist(expectedCollectionAddress)244 .call()).to.be.false;245246 await collectionHelpers.methods247 .createCollection([248 'A',249 'A',250 'A',251 CollectionMode.Nonfungible,252 0,253 ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,254 ])255 .send({value: Number(2n * helper.balance.getOneTokenNominal())});256257 expect(await collectionHelpers.methods258 .isCollectionExist(expectedCollectionAddress)259 .call()).to.be.true;260 });261 });262263 describe('Create RFT collection from EVM', () => {264 before(async function() {265 await usingEthPlaygrounds((helper) => {266 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);267 return Promise.resolve();268 });269 });270271 itEth('Create collection', async ({helper}) => {272 const owner = await helper.eth.createAccountWithBalance(donor);273274 const name = 'CollectionEVM';275 const description = 'Some description';276 const prefix = 'token prefix';277278 const {collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData(name, description, prefix, 'rft')).send();279 const data = (await helper.rft.getData(collectionId))!;280 const collection = helper.rft.getCollectionObject(collectionId);281282 expect(data.name).to.be.eq(name);283 expect(data.description).to.be.eq(description);284 expect(data.raw.tokenPrefix).to.be.eq(prefix);285 expect(data.raw.mode).to.be.eq('ReFungible');286287 const options = await collection.getOptions();288289 expect(options.tokenPropertyPermissions).to.be.empty;290 });291292 itEth('Create collection with properties & get description', async ({helper}) => {293 const owner = await helper.eth.createAccountWithBalance(donor);294295 const name = 'CollectionEVM';296 const description = 'Some description';297 const prefix = 'token prefix';298 const baseUri = 'BaseURI';299300 const {collectionId, collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, name, description, prefix, baseUri);301 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');302303 const collection = helper.rft.getCollectionObject(collectionId);304 const data = (await collection.getData())!;305306 expect(data.name).to.be.eq(name);307 expect(data.description).to.be.eq(description);308 expect(data.raw.tokenPrefix).to.be.eq(prefix);309 expect(data.raw.mode).to.be.eq('ReFungible');310311 expect(await contract.methods.description().call()).to.deep.equal(description);312313 const options = await collection.getOptions();314 expect(options.tokenPropertyPermissions).to.be.deep.equal([315 {316 key: 'URI',317 permission: {mutable: true, collectionAdmin: true, tokenOwner: false},318 },319 {320 key: 'URISuffix',321 permission: {mutable: true, collectionAdmin: true, tokenOwner: false},322 },323 ]);324 });325326 itEth('Set sponsorship', async ({helper}) => {327 const owner = await helper.eth.createAccountWithBalance(donor);328 const sponsor = await helper.eth.createAccountWithBalance(donor);329 const ss58Format = helper.chain.getChainProperties().ss58Format;330 const {collectionId, collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Sponsor', 'absolutely anything', 'ENVY', 'rft')).send();331332 const collection = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);333 const sponsorCross = helper.ethCrossAccount.fromAddress(sponsor);334 await collection.methods.setCollectionSponsorCross(sponsorCross).send();335336 let data = (await helper.rft.getData(collectionId))!;337 expect(data.raw.sponsorship.Unconfirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format)));338339 await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');340341 const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor);342 await sponsorCollection.methods.confirmCollectionSponsorship().send();343344 data = (await helper.rft.getData(collectionId))!;345 expect(data.raw.sponsorship.Confirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format)));346 });347348 itEth('Collection address exist', async ({helper}) => {349 const owner = await helper.eth.createAccountWithBalance(donor);350 const collectionAddressForNonexistentCollection = '0x17C4E6453CC49AAAAEACA894E6D9683E00112233';351 const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);352353 expect(await collectionHelpers354 .methods.isCollectionExist(collectionAddressForNonexistentCollection).call())355 .to.be.false;356357 const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Exister', 'absolutely anything', 'WIWT', 'rft')).send();358 expect(await collectionHelpers359 .methods.isCollectionExist(collectionAddress).call())360 .to.be.true;361362 // check collectionOwner:363 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true);364 const collectionOwner = await collectionEvm.methods.collectionOwner().call();365 expect(helper.address.restoreCrossAccountFromBigInt(BigInt(collectionOwner.sub))).to.eq(helper.address.ethToSubstrate(owner, true));366 });367368 itEth('destroyCollection', async ({helper}) => {369 const owner = await helper.eth.createAccountWithBalance(donor);370 const {collectionAddress, collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData('Limits', 'absolutely anything', 'OLF', 'rft')).send();371 const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);372373 await expect(collectionHelper.methods374 .destroyCollection(collectionAddress)375 .send({from: owner})).to.be.fulfilled;376377 expect(await collectionHelper.methods378 .isCollectionExist(collectionAddress)379 .call()).to.be.false;380 expect(await helper.collection.getData(collectionId)).to.be.null;381 });382383 itEth('(!negative test!) Create collection (bad lengths)', async ({helper}) => {384 const owner = await helper.eth.createAccountWithBalance(donor);385 const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);386 {387 const MAX_NAME_LENGTH = 64;388 const collectionName = 'A'.repeat(MAX_NAME_LENGTH + 1);389 const description = 'A';390 const tokenPrefix = 'A';391392 await expect(collectionHelper.methods393 .createCollection([394 collectionName,395 description,396 tokenPrefix,397 CollectionMode.Refungible,398 DECIMALS,399 ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,400 ])401 .call({value: Number(2n * nominal)})).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGTH);402 }403 {404 const MAX_DESCRIPTION_LENGTH = 256;405 const collectionName = 'A';406 const description = 'A'.repeat(MAX_DESCRIPTION_LENGTH + 1);407 const tokenPrefix = 'A';408 await expect(collectionHelper.methods409 .createCollection([410 collectionName,411 description,412 tokenPrefix,413 CollectionMode.Refungible,414 DECIMALS,415 ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,416 ])417 .call({value: Number(2n * nominal)})).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGTH);418 }419 {420 const MAX_TOKEN_PREFIX_LENGTH = 16;421 const collectionName = 'A';422 const description = 'A';423 const tokenPrefix = 'A'.repeat(MAX_TOKEN_PREFIX_LENGTH + 1);424 await expect(collectionHelper.methods425 .createCollection([426 collectionName,427 description,428 tokenPrefix,429 CollectionMode.Refungible,430 DECIMALS,431 ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,432 ])433 .call({value: Number(2n * nominal)})).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGTH);434 }435 });436437 itEth('(!negative test!) Create collection (no funds)', async ({helper}) => {438 const owner = await helper.eth.createAccountWithBalance(donor);439 const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);440 await expect(collectionHelper.methods441 .createCollection([442 'Peasantry',443 'absolutely anything',444 'TWIW',445 CollectionMode.Refungible,446 0,447 ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,448 ])449 .call({value: Number(1n * nominal)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');450 });451 });452453 describe('Sponsoring', () => {454 itEth('Сan remove collection sponsor', async ({helper}) => {455 const owner = await helper.eth.createAccountWithBalance(donor);456 const sponsor = await helper.eth.createAccountWithBalance(donor);457 const sponsorCross = helper.ethCrossAccount.fromAddr(sponsor);458459 const {collectionAddress} = await helper.eth.createCollection(460 owner,461 {462 ...CREATE_COLLECTION_DATA_DEFAULTS,463 name: 'Sponsor collection',464 description: '1',465 tokenPrefix: '1',466 collectionMode: 'nft',467 pendingSponsor: sponsorCross,468 },469 ).send();470 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);471472 expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.true;473474 await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});475 let sponsorStruct = await collectionEvm.methods.collectionSponsor().call({from: owner});476 expect(helper.address.restoreCrossAccountFromBigInt(BigInt(sponsorStruct.sub))).to.be.eq(helper.address.ethToSubstrate(sponsor, true));477 expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false;478479 await collectionEvm.methods.removeCollectionSponsor().send({from: owner});480481 sponsorStruct = await collectionEvm.methods.collectionSponsor().call({from: owner});482 expect(sponsorStruct.eth).to.be.eq('0x0000000000000000000000000000000000000000');483 });484485 itEth('Can sponsor from evm address via access list', async ({helper}) => {486 const owner = await helper.eth.createAccountWithBalance(donor);487 const sponsorEth = await helper.eth.createAccountWithBalance(donor);488 const sponsorCross = helper.ethCrossAccount.fromAddr(sponsorEth);489490 const {collectionId, collectionAddress} = await helper.eth.createERC721MetadataCompatibleCollection(491 owner,492 {493 ...CREATE_COLLECTION_DATA_DEFAULTS,494 name: 'Sponsor collection',495 description: '1',496 tokenPrefix: '1',497 collectionMode: 'nft',498 pendingSponsor: sponsorCross,499 limits: [{field: CollectionLimitField.SponsoredDataRateLimit, value: 30n}],500 tokenPropertyPermissions: [{key: 'key', permissions: [{code: TokenPermissionField.TokenOwner, value: true}]}],501 },502 '',503 );504505 const collectionSub = helper.nft.getCollectionObject(collectionId);506 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);507508 let sponsorship = (await collectionSub.getData())!.raw.sponsorship;509 expect(sponsorship.Unconfirmed).to.be.eq(helper.address.ethToSubstrate(sponsorEth, true));510 // Account cannot confirm sponsorship if it is not set as a sponsor511 await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');512513 // Sponsor can confirm sponsorship:514 await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsorEth});515 sponsorship = (await collectionSub.getData())!.raw.sponsorship;516 expect(sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsorEth, true));517518 // Create user with no balance:519 const user = helper.ethCrossAccount.createAccount();520 const nextTokenId = await collectionEvm.methods.nextTokenId().call();521 expect(nextTokenId).to.be.equal('1');522523 // Set collection permissions:524 const oldPermissions = (await collectionSub.getData())!.raw.permissions;525 expect(oldPermissions.mintMode).to.be.false;526 expect(oldPermissions.access).to.be.equal('Normal');527528 await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner});529 await collectionEvm.methods.addToCollectionAllowListCross(user).send({from: owner});530 await collectionEvm.methods.setCollectionMintMode(true).send({from: owner});531532 const newPermissions = (await collectionSub.getData())!.raw.permissions;533 expect(newPermissions.mintMode).to.be.true;534 expect(newPermissions.access).to.be.equal('AllowList');535536 const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));537 const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth));538 const userBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user.eth));539540 // User can mint token without balance:541 {542 const result = await collectionEvm.methods.mintCross(user, [{key: 'key', value: Buffer.from('Value')}]).send({from: user.eth});543 const event = helper.eth.normalizeEvents(result.events)544 .find(event => event.event === 'Transfer');545546 expect(event).to.be.deep.equal({547 address: collectionAddress,548 event: 'Transfer',549 args: {550 from: '0x0000000000000000000000000000000000000000',551 to: user.eth,552 tokenId: '1',553 },554 });555556 // await collectionEvm.methods.setProperties(1, [{key: 'key', value: Buffer.from('Value1')}]).send({from: user.eth});557558 const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));559 const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth));560 const userBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user.eth));561562 expect(await collectionEvm.methods.properties(nextTokenId, []).call())563 .to.be.like([564 [565 'key',566 '0x' + Buffer.from('Value').toString('hex'),567 ],568 ]);569 expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter);570 expect(userBalanceAfter).to.be.eq(userBalanceBefore);571 expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true;572 }573 });574575 itEth('Check that transaction via EVM spend money from sponsor address', async ({helper}) => {576 const owner = await helper.eth.createAccountWithBalance(donor);577 const sponsor = await helper.eth.createAccountWithBalance(donor);578 const sponsorCross = helper.ethCrossAccount.fromAddr(sponsor);579 const user = helper.eth.createAccount();580 const userCross = helper.ethCrossAccount.fromAddress(user);581582 const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleCollection(583 owner,584 {585 ...CREATE_COLLECTION_DATA_DEFAULTS,586 name: 'Sponsor collection',587 description: '1',588 tokenPrefix: '1',589 collectionMode: 'nft',590 pendingSponsor: sponsorCross,591 adminList: [userCross],592 },593 '',594 );595596 const collectionSub = helper.nft.getCollectionObject(collectionId);597 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);598 // Set collection sponsor:599 let collectionData = (await collectionSub.getData())!;600 expect(collectionData.raw.sponsorship.Unconfirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true));601 await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');602603 await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});604 collectionData = (await collectionSub.getData())!;605 expect(collectionData.raw.sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true));606607 const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));608 const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));609610 const mintingResult = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user});611 const tokenId = mintingResult.events.Transfer.returnValues.tokenId;612613 const event = helper.eth.normalizeEvents(mintingResult.events)614 .find(event => event.event === 'Transfer');615 const address = helper.ethAddress.fromCollectionId(collectionId);616617 expect(event).to.be.deep.equal({618 address,619 event: 'Transfer',620 args: {621 from: '0x0000000000000000000000000000000000000000',622 to: user,623 tokenId: '1',624 },625 });626 expect(await collectionEvm.methods.tokenURI(tokenId).call({from: user})).to.be.equal('Test URI');627628 const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));629 expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);630 const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));631 expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;632 });633634 itEth('Can reassign collection sponsor', async ({helper}) => {635 const owner = await helper.eth.createAccountWithBalance(donor);636 const sponsorEth = await helper.eth.createAccountWithBalance(donor);637 const sponsorCrossEth = helper.ethCrossAccount.fromAddr(sponsorEth);638 const [sponsorSub] = await helper.arrange.createAccounts([100n], donor);639 const sponsorCrossSub = helper.ethCrossAccount.fromKeyringPair(sponsorSub);640641 const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleCollection(642 owner,643 {644 ...CREATE_COLLECTION_DATA_DEFAULTS,645 name: 'Sponsor collection',646 description: '1',647 tokenPrefix: '1',648 collectionMode: 'nft',649 pendingSponsor: sponsorCrossEth,650 },651 '',652 );653 const collectionSub = helper.nft.getCollectionObject(collectionId);654 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);655656 // Set and confirm sponsor:657 await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsorEth});658659 // Can reassign sponsor:660 await collectionEvm.methods.setCollectionSponsorCross(sponsorCrossSub).send({from: owner});661 const collectionSponsor = (await collectionSub.getData())?.raw.sponsorship;662 expect(collectionSponsor).to.deep.eq({Unconfirmed: sponsorSub.address});663 });664665 [666 'transfer',667 'transferCross',668 'transferFrom',669 'transferFromCross',670 ].map(testCase =>671 itEth(`[${testCase}] Check that transfer via EVM spend money from sponsor address`, async ({helper}) => {672 const owner = await helper.eth.createAccountWithBalance(donor);673 const sponsor = await helper.eth.createAccountWithBalance(donor);674 const sponsorCross = helper.ethCrossAccount.fromAddr(sponsor);675 const user = await helper.eth.createAccountWithBalance(donor);676 const userCross = helper.ethCrossAccount.fromAddress(user);677678 const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleCollection(679 owner,680 {681 ...CREATE_COLLECTION_DATA_DEFAULTS,682 name: 'Sponsor collection',683 description: '1',684 tokenPrefix: '1',685 collectionMode: 'rft',686 pendingSponsor: sponsorCross,687 adminList: [userCross],688 },689 '',690 );691 const receiver = await helper.eth.createAccountWithBalance(donor);692 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);693694 await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});695696 const result = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user});697 const tokenId = result.events.Transfer.returnValues.tokenId;698699 const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));700 const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));701 const userBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));702703 switch (testCase) {704 case 'transfer':705 await collectionEvm.methods.transfer(receiver, tokenId).send({from: user});706 break;707 case 'transferCross':708 await collectionEvm.methods.transferCross(helper.ethCrossAccount.fromAddress(receiver), tokenId).send({from: user});709 break;710 case 'transferFrom':711 await collectionEvm.methods.transferFrom(user, receiver, tokenId).send({from: user});712 break;713 case 'transferFromCross':714 await collectionEvm.methods.transferFromCross(helper.ethCrossAccount.fromAddress(user), helper.ethCrossAccount.fromAddress(receiver), tokenId).send({from: user});715 break;716 }717718 const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));719 expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);720 const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));721 expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;722 const userBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));723 expect(userBalanceAfter).to.be.eq(userBalanceBefore);724 }));725 });726727 describe('Collection admins', () => {728 let donor: IKeyringPair;729730 before(async function() {731 await usingEthPlaygrounds(async (_helper, privateKey) => {732 donor = await privateKey({url: import.meta.url});733 });734 });735736 [737 {mode: 'nft' as const, requiredPallets: []},738 {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},739 {mode: 'ft' as const, requiredPallets: []},740 ].map(testCase => {741 itEth.ifWithPallets(`can add account admin by owner for ${testCase.mode}`, testCase.requiredPallets, async ({helper, privateKey}) => {742 // arrange743 const owner = await helper.eth.createAccountWithBalance(donor);744 const adminSub = await privateKey('//admin2');745 const adminEth = helper.eth.createAccount().toLowerCase();746747 const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub);748 const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth);749750 const {collectionAddress, collectionId} = await helper.eth.createCollection(751 owner,752 {753 ...CREATE_COLLECTION_DATA_DEFAULTS,754 name: 'Mint collection',755 description: 'a',756 tokenPrefix: 'b',757 collectionMode: testCase.mode,758 adminList: [adminCrossSub, adminCrossEth],759 },760 ).send();761 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner, true);762763 // 1. Expect api.rpc.unique.adminlist returns admins:764 const adminListRpc = await helper.collection.getAdmins(collectionId);765 expect(adminListRpc).to.has.length(2);766 expect(adminListRpc).to.be.deep.contain.members([{Substrate: adminSub.address}, {Ethereum: adminEth}]);767768 // 2. Expect methods.collectionAdmins == api.rpc.unique.adminlist769 let adminListEth = await collectionEvm.methods.collectionAdmins().call();770 adminListEth = adminListEth.map((element: IEthCrossAccountId) => helper.address.convertCrossAccountFromEthCrossAccount(element));771 expect(adminListRpc).to.be.like(adminListEth);772773 // 3. check isOwnerOrAdminCross returns true:774 expect(await collectionEvm.methods.isOwnerOrAdminCross(adminCrossSub).call()).to.be.true;775 expect(await collectionEvm.methods.isOwnerOrAdminCross(adminCrossEth).call()).to.be.true;776 });777 });778779 itEth('cross account admin can mint', async ({helper}) => {780 // arrange: create collection and accounts781 const owner = await helper.eth.createAccountWithBalance(donor);782 const adminEth = (await helper.eth.createAccountWithBalance(donor)).toLowerCase();783 const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth);784 const [adminSub] = await helper.arrange.createAccounts([100n], donor);785 const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub);786 const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleCollection(787 owner,788 {789 ...CREATE_COLLECTION_DATA_DEFAULTS,790 name: 'Mint collection',791 description: 'a',792 tokenPrefix: 'b',793 collectionMode: 'nft',794 adminList: [adminCrossSub, adminCrossEth],795 },796 'uri',797 );798 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner, true);799800 // admin (sub and eth) can mint token:801 await collectionEvm.methods.mint(owner).send({from: adminEth});802 await helper.nft.mintToken(adminSub, {collectionId, owner: {Ethereum: owner}});803804 expect(await helper.collection.getLastTokenId(collectionId)).to.eq(2);805 });806807 itEth('cannot add invalid cross account admin', async ({helper}) => {808 const owner = await helper.eth.createAccountWithBalance(donor);809 const [admin] = await helper.arrange.createAccounts([100n, 100n], donor);810811 const adminCross = {812 eth: helper.address.substrateToEth(admin.address),813 sub: admin.addressRaw,814 };815816 await expect(helper.eth.createCollection(817 owner,818 {819 ...CREATE_COLLECTION_DATA_DEFAULTS,820 name: 'A',821 description: 'B',822 tokenPrefix: 'C',823 collectionMode: 'nft',824 adminList: [adminCross],825 },826 ).call()).to.be.rejected;827 });828829 itEth('Remove [cross] admin by owner', async ({helper}) => {830 const owner = await helper.eth.createAccountWithBalance(donor);831832 const [adminSub] = await helper.arrange.createAccounts([10n], donor);833 const adminEth = (await helper.eth.createAccountWithBalance(donor)).toLowerCase();834 const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub);835 const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth);836837 const {collectionAddress, collectionId} = await helper.eth.createCollection(838 owner,839 {840 ...CREATE_COLLECTION_DATA_DEFAULTS,841 name: 'A',842 description: 'B',843 tokenPrefix: 'C',844 collectionMode: 'nft',845 adminList: [adminCrossSub, adminCrossEth],846 },847 ).send();848849 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);850851 {852 const adminList = await helper.collection.getAdmins(collectionId);853 expect(adminList).to.deep.include({Substrate: adminSub.address});854 expect(adminList).to.deep.include({Ethereum: adminEth});855 }856857 await collectionEvm.methods.removeCollectionAdminCross(adminCrossSub).send();858 await collectionEvm.methods.removeCollectionAdminCross(adminCrossEth).send();859 const adminList = await helper.collection.getAdmins(collectionId);860 expect(adminList.length).to.be.eq(0);861862 // Non admin cannot mint:863 await expect(helper.nft.mintToken(adminSub, {collectionId, owner: {Substrate: adminSub.address}})).to.be.rejectedWith(/common.PublicMintingNotAllowed/);864 await expect(collectionEvm.methods.mint(adminEth).send({from: adminEth})).to.be.rejected;865 });866 });867868 describe('Collection limits', () => {869 describe('Can set collection limits', () => {870 [871 {case: 'nft' as const},872 {case: 'rft' as const, requiredPallets: [Pallets.ReFungible]},873 {case: 'ft' as const},874 ].map(testCase =>875 itEth.ifWithPallets(`for ${testCase.case}`, testCase.requiredPallets || [], async ({helper}) => {876 const owner = await helper.eth.createAccountWithBalance(donor);877878 const limits = {879 accountTokenOwnershipLimit: 1000n,880 sponsoredDataSize: 1024n,881 sponsoredDataRateLimit: 30n,882 tokenLimit: 1000000n,883 sponsorTransferTimeout: 6n,884 sponsorApproveTimeout: 6n,885 ownerCanTransfer: 1n,886 ownerCanDestroy: 0n,887 transfersEnabled: 0n,888 };889890 const {collectionId, collectionAddress} = await helper.eth.createCollection(891 owner,892 {893 ...CREATE_COLLECTION_DATA_DEFAULTS,894 name: 'Limits',895 description: 'absolutely anything',896 tokenPrefix: 'FLO',897 collectionMode: testCase.case,898 limits: [899 {field: CollectionLimitField.AccountTokenOwnership, value: limits.accountTokenOwnershipLimit},900 {field: CollectionLimitField.SponsoredDataSize, value: limits.sponsoredDataSize},901 {field: CollectionLimitField.SponsoredDataRateLimit, value: limits.sponsoredDataRateLimit},902 {field: CollectionLimitField.TokenLimit, value: limits.tokenLimit},903 {field: CollectionLimitField.SponsorTransferTimeout, value: limits.sponsorTransferTimeout},904 {field: CollectionLimitField.SponsorApproveTimeout, value: limits.sponsorApproveTimeout},905 {field: CollectionLimitField.OwnerCanTransfer, value: limits.ownerCanTransfer},906 {field: CollectionLimitField.OwnerCanDestroy, value: limits.ownerCanDestroy},907 {field: CollectionLimitField.TransferEnabled, value: limits.transfersEnabled},908 ],909 },910 ).send();911912 const expectedLimits = {913 accountTokenOwnershipLimit: 1000,914 sponsoredDataSize: 1024,915 sponsoredDataRateLimit: {blocks: 30},916 tokenLimit: 1000000,917 sponsorTransferTimeout: 6,918 sponsorApproveTimeout: 6,919 ownerCanTransfer: true,920 ownerCanDestroy: false,921 transfersEnabled: false,922 };923924 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.case, owner);925926 // Check limits from sub:927 const data = (await helper.rft.getData(collectionId))!;928 expect(data.raw.limits).to.deep.eq(expectedLimits);929 expect(await helper.collection.getEffectiveLimits(collectionId)).to.deep.eq(expectedLimits);930 // Check limits from eth:931 const limitsEvm = await collectionEvm.methods.collectionLimits().call({from: owner});932 expect(limitsEvm).to.have.length(9);933 expect(limitsEvm[0]).to.deep.eq([CollectionLimitField.AccountTokenOwnership.toString(), [true, limits.accountTokenOwnershipLimit.toString()]]);934 expect(limitsEvm[1]).to.deep.eq([CollectionLimitField.SponsoredDataSize.toString(), [true, limits.sponsoredDataSize.toString()]]);935 expect(limitsEvm[2]).to.deep.eq([CollectionLimitField.SponsoredDataRateLimit.toString(), [true, limits.sponsoredDataRateLimit.toString()]]);936 expect(limitsEvm[3]).to.deep.eq([CollectionLimitField.TokenLimit.toString(), [true, limits.tokenLimit.toString()]]);937 expect(limitsEvm[4]).to.deep.eq([CollectionLimitField.SponsorTransferTimeout.toString(), [true, limits.sponsorTransferTimeout.toString()]]);938 expect(limitsEvm[5]).to.deep.eq([CollectionLimitField.SponsorApproveTimeout.toString(), [true, limits.sponsorApproveTimeout.toString()]]);939 expect(limitsEvm[6]).to.deep.eq([CollectionLimitField.OwnerCanTransfer.toString(), [true, limits.ownerCanTransfer.toString()]]);940 expect(limitsEvm[7]).to.deep.eq([CollectionLimitField.OwnerCanDestroy.toString(), [true, limits.ownerCanDestroy.toString()]]);941 expect(limitsEvm[8]).to.deep.eq([CollectionLimitField.TransferEnabled.toString(), [true, limits.transfersEnabled.toString()]]);942 }));943 });944945 describe('(!negative test!) Cannot set invalid collection limits', () => {946 [947 {case: 'nft' as const},948 {case: 'rft' as const, requiredPallets: [Pallets.ReFungible]},949 {case: 'ft' as const},950 ].map(testCase =>951 itEth.ifWithPallets(`for ${testCase.case}`, testCase.requiredPallets || [], async ({helper}) => {952 const invalidLimits = {953 accountTokenOwnershipLimit: BigInt(Number.MAX_SAFE_INTEGER),954 transfersEnabled: 3,955 };956957 const createCollectionData = {958 ...CREATE_COLLECTION_DATA_DEFAULTS,959 name: 'Limits',960 description: 'absolutely anything',961 tokenPrefix: 'ISNI',962 collectionMode: testCase.case,963 };964965 const owner = await helper.eth.createAccountWithBalance(donor);966 await expect(helper.eth.createCollection(967 owner,968 {969 ...createCollectionData,970 limits: [{field: 9 as CollectionLimitField, value: 1n}],971 },972 ).call()).to.be.rejectedWith('value not convertible into enum "CollectionLimitField"');973974 await expect(helper.eth.createCollection(975 owner,976 {977 ...createCollectionData,978 limits: [{field: CollectionLimitField.AccountTokenOwnership, value: invalidLimits.accountTokenOwnershipLimit}],979 },980 ).call()).to.be.rejectedWith(`can't convert value to u32 "${invalidLimits.accountTokenOwnershipLimit}"`);981982 await expect(helper.eth.createCollection(983 owner,984 {985 ...createCollectionData,986 limits: [{field: CollectionLimitField.TransferEnabled, value: 3n}],987 },988 ).call()).to.be.rejectedWith(`can't convert value to boolean "${invalidLimits.transfersEnabled}"`);989990 await expect(helper.eth.createCollection(991 owner,992 {993 ...createCollectionData,994 limits: [{field: CollectionLimitField.SponsoredDataSize, value: -1n}],995 },996 ).call()).to.be.rejectedWith('value out-of-bounds');997 }));998 });999 });10001001 describe('Collection properties', () => {10021003 [1004 {mode: 'nft' as const, methodParams: [{key: 'testKey1', value: Buffer.from('testValue1')}, {key: 'testKey2', value: Buffer.from('testValue2')}], expectedProps: [{key: 'testKey1', value: 'testValue1'}, {key: 'testKey2', value: 'testValue2'}]},1005 {mode: 'rft' as const, methodParams: [{key: 'testKey1', value: Buffer.from('testValue1')}, {key: 'testKey2', value: Buffer.from('testValue2')}], expectedProps: [{key: 'testKey1', value: 'testValue1'}, {key: 'testKey2', value: 'testValue2'}]},1006 {mode: 'ft' as const, methodParams: [{key: 'testKey1', value: Buffer.from('testValue1')}, {key: 'testKey2', value: Buffer.from('testValue2')}], expectedProps: [{key: 'testKey1', value: 'testValue1'}, {key: 'testKey2', value: 'testValue2'}]},1007 ].map(testCase =>1008 itEth.ifWithPallets(`Collection properties can be set for ${testCase.mode}`, testCase.mode === 'rft' ? [Pallets.ReFungible] : [], async({helper}) => {1009 const caller = await helper.eth.createAccountWithBalance(donor);1010 const callerCross = helper.ethCrossAccount.fromAddress(caller);1011 const owner = await helper.eth.createAccountWithBalance(donor);1012 const {collectionId, collectionAddress} = await helper.eth.createCollection(1013 owner,1014 {1015 ...CREATE_COLLECTION_DATA_DEFAULTS,1016 name: 'name',1017 description: 'test',1018 tokenPrefix: 'test',1019 collectionMode: testCase.mode,1020 adminList: [callerCross],1021 properties: testCase.methodParams,1022 },1023 ).send();10241025 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, caller);10261027 const raw = (await helper[testCase.mode].getData(collectionId))?.raw;1028 expect(raw.properties).to.deep.equal(testCase.expectedProps);10291030 // collectionProperties returns properties:1031 expect(await collectionEvm.methods.collectionProperties([]).call()).to.be.like(testCase.expectedProps.map(prop => helper.ethProperty.property(prop.key, prop.value)));1032 }));10331034 [1035 {mode: 'nft' as const},1036 {mode: 'rft' as const},1037 {mode: 'ft' as const},1038 ].map(testCase =>1039 itEth.ifWithPallets(`Collection properties can be deleted for ${testCase.mode}`, testCase.mode === 'rft' ? [Pallets.ReFungible] : [], async({helper}) => {1040 const caller = await helper.eth.createAccountWithBalance(donor);1041 const callerCross = helper.ethCrossAccount.fromAddress(caller);1042 const owner = await helper.eth.createAccountWithBalance(donor);1043 const {collectionId, collectionAddress} = await helper.eth.createCollection(1044 owner,1045 {1046 ...CREATE_COLLECTION_DATA_DEFAULTS,1047 name: 'name',1048 description: 'test',1049 tokenPrefix: 'test',1050 collectionMode: testCase.mode,1051 adminList: [callerCross],1052 properties:[1053 {key: 'testKey1', value: Buffer.from('testValue1')},1054 {key: 'testKey2', value: Buffer.from('testValue2')},1055 {key: 'testKey3', value: Buffer.from('testValue3')}],1056 },1057 ).send();10581059 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, testCase.mode, caller);10601061 await collectionEvm.methods.deleteCollectionProperties(['testKey1', 'testKey2']).send({from: caller});10621063 const raw = (await helper[testCase.mode].getData(collectionId))?.raw;10641065 expect(raw.properties.length).to.equal(1);1066 expect(raw.properties).to.deep.equal([{key: 'testKey3', value: 'testValue3'}]);1067 }));10681069 itEth('(!negative test!) Cannot set invalid properties', async({helper}) => {1070 const caller = await helper.eth.createAccountWithBalance(donor);1071 const callerCross = helper.ethCrossAccount.fromAddress(caller);1072 const owner = await helper.eth.createAccountWithBalance(donor);1073 const createCollectionData = {1074 ...CREATE_COLLECTION_DATA_DEFAULTS,1075 name: 'name',1076 description: 'test',1077 tokenPrefix: 'test',1078 collectionMode: 'nft' as TCollectionMode,1079 adminList: [callerCross],1080 };1081 await expect(helper.eth.createCollection(1082 owner,1083 {1084 ...createCollectionData,1085 properties: [{key: '', value: Buffer.from('val1')}],1086 },1087 ).call()).to.be.rejected;10881089 await expect(helper.eth.createCollection(1090 owner,1091 {1092 ...createCollectionData,1093 properties: [{key: 'déjà vu', value: Buffer.from('hmm...')}],1094 },1095 ).call()).to.be.rejected;10961097 await expect(helper.eth.createCollection(1098 owner,1099 {1100 ...createCollectionData,1101 properties: [{key: 'a'.repeat(257), value: Buffer.from('val3')}],1102 },1103 ).call()).to.be.rejected;1104 });11051106 itEth('(!negative test!) cannot delete properties of non-owned collections', async ({helper}) => {1107 const caller = await helper.eth.createAccountWithBalance(donor);1108 const owner = await helper.eth.createAccountWithBalance(donor);1109 const {collectionAddress} = await helper.eth.createCollection(1110 owner,1111 {1112 ...CREATE_COLLECTION_DATA_DEFAULTS,1113 name: 'name',1114 description: 'test',1115 tokenPrefix: 'test',1116 collectionMode: 'nft',1117 properties:[1118 {key: 'testKey1', value: Buffer.from('testValue1')},1119 {key: 'testKey2', value: Buffer.from('testValue2')}],1120 },1121 ).send();11221123 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller);11241125 await expect(collectionEvm.methods.deleteCollectionProperties(['testKey2']).send({from: caller})).to.be.rejected;1126 });1127 });11281129 describe('Token property permissions', () => {1130 [1131 {mode: 'nft' as const, requiredPallets: []},1132 {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},1133 ].map(testCase =>1134 itEth.ifWithPallets(`[${testCase.mode}] Can set all possible token property permissions`, testCase.requiredPallets, async({helper}) => {1135 const owner = await helper.eth.createAccountWithBalance(donor);1136 const caller = await helper.ethCrossAccount.createAccountWithBalance(donor);1137 for(const [mutable,collectionAdmin, tokenOwner] of cartesian([], [false, true], [false, true], [false, true])) {1138 const {collectionId, collectionAddress} = await helper.eth.createCollection(1139 owner,1140 {1141 ...CREATE_COLLECTION_DATA_DEFAULTS,1142 name: 'A',1143 description: 'B',1144 tokenPrefix: 'C',1145 collectionMode: testCase.mode,1146 adminList: [caller],1147 tokenPropertyPermissions: [1148 {1149 key: 'testKey',1150 permissions: [1151 {code: TokenPermissionField.Mutable, value: mutable},1152 {code: TokenPermissionField.TokenOwner, value: tokenOwner},1153 {code: TokenPermissionField.CollectionAdmin, value: collectionAdmin},1154 ],1155 },1156 ],1157 },1158 ).send();1159 const collection = helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner);11601161 expect(await helper[testCase.mode].getPropertyPermissions(collectionId)).to.be.deep.equal([{1162 key: 'testKey',1163 permission: {mutable, collectionAdmin, tokenOwner},1164 }]);11651166 expect(await collection.methods.tokenPropertyPermissions().call({from: caller.eth})).to.be.like([1167 ['testKey', [1168 [TokenPermissionField.Mutable.toString(), mutable],1169 [TokenPermissionField.TokenOwner.toString(), tokenOwner],1170 [TokenPermissionField.CollectionAdmin.toString(), collectionAdmin]],1171 ],1172 ]);1173 }1174 }));11751176 [1177 {mode: 'nft' as const, requiredPallets: []},1178 {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},1179 ].map(testCase =>1180 itEth.ifWithPallets(`[${testCase.mode}] Can set multiple token property permissions`, testCase.requiredPallets, async({helper}) => {1181 const owner = await helper.eth.createAccountWithBalance(donor);1182 const {collectionId, collectionAddress} = await helper.eth.createCollection(1183 owner,1184 {1185 ...CREATE_COLLECTION_DATA_DEFAULTS,1186 name: 'A',1187 description: 'B',1188 tokenPrefix: 'C',1189 collectionMode: testCase.mode,1190 tokenPropertyPermissions: [1191 {1192 key: 'testKey_0',1193 permissions: [1194 {code: TokenPermissionField.Mutable, value: true},1195 {code: TokenPermissionField.TokenOwner, value: true},1196 {code: TokenPermissionField.CollectionAdmin, value: true}],1197 },1198 {1199 key: 'testKey_1',1200 permissions: [1201 {code: TokenPermissionField.Mutable, value: true},1202 {code: TokenPermissionField.TokenOwner, value: false},1203 {code: TokenPermissionField.CollectionAdmin, value: true}],1204 },1205 {1206 key: 'testKey_2',1207 permissions: [1208 {code: TokenPermissionField.Mutable, value: false},1209 {code: TokenPermissionField.TokenOwner, value: true},1210 {code: TokenPermissionField.CollectionAdmin, value: false}],1211 },1212 ],1213 },1214 ).send();1215 const collection = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner);12161217 expect(await helper[testCase.mode].getPropertyPermissions(collectionId)).to.be.deep.equal([1218 {1219 key: 'testKey_0',1220 permission: {mutable: true, tokenOwner: true, collectionAdmin: true},1221 },1222 {1223 key: 'testKey_1',1224 permission: {mutable: true, tokenOwner: false, collectionAdmin: true},1225 },1226 {1227 key: 'testKey_2',1228 permission: {mutable: false, tokenOwner: true, collectionAdmin: false},1229 },1230 ]);12311232 expect(await collection.methods.tokenPropertyPermissions().call({from: owner})).to.be.like([1233 ['testKey_0', [1234 [TokenPermissionField.Mutable.toString(), true],1235 [TokenPermissionField.TokenOwner.toString(), true],1236 [TokenPermissionField.CollectionAdmin.toString(), true]],1237 ],1238 ['testKey_1', [1239 [TokenPermissionField.Mutable.toString(), true],1240 [TokenPermissionField.TokenOwner.toString(), false],1241 [TokenPermissionField.CollectionAdmin.toString(), true]],1242 ],1243 ['testKey_2', [1244 [TokenPermissionField.Mutable.toString(), false],1245 [TokenPermissionField.TokenOwner.toString(), true],1246 [TokenPermissionField.CollectionAdmin.toString(), false]],1247 ],1248 ]);1249 }));12501251 [1252 {mode: 'nft' as const, requiredPallets: []},1253 {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},1254 ].map(testCase =>1255 itEth.ifWithPallets(`Can be deleted for ${testCase.mode}`, testCase.requiredPallets, async({helper}) => {1256 const caller = await helper.eth.createAccountWithBalance(donor);1257 const receiver = await helper.ethCrossAccount.createAccountWithBalance(donor);1258 const {collectionAddress} = await helper.eth.createCollection(1259 caller,1260 {1261 ...CREATE_COLLECTION_DATA_DEFAULTS,1262 name: 'A',1263 description: 'B',1264 tokenPrefix: 'C',1265 collectionMode: testCase.mode,1266 adminList: [receiver],1267 tokenPropertyPermissions: [1268 {1269 key: 'testKey',1270 permissions: [1271 {code: TokenPermissionField.Mutable, value: true},1272 {code: TokenPermissionField.CollectionAdmin, value: true}],1273 },1274 {1275 key: 'testKey_1',1276 permissions: [1277 {code: TokenPermissionField.Mutable, value: true},1278 {code: TokenPermissionField.CollectionAdmin, value: true}],1279 },1280 ],1281 },1282 ).send();12831284 const collection = helper.ethNativeContract.collection(collectionAddress, testCase.mode, caller);1285 const tokenId = (await collection.methods.mintCross(receiver, [{key: 'testKey', value: Buffer.from('testValue')}, {key: 'testKey_1', value: Buffer.from('testValue_1')}]).send()).events.Transfer.returnValues.tokenId;1286 expect(await collection.methods.properties(tokenId, ['testKey', 'testKey_1']).call()).to.has.length(2);12871288 await collection.methods.deleteProperties(tokenId, ['testKey', 'testKey_1']).send({from: caller});1289 expect(await collection.methods.properties(tokenId, ['testKey', 'testKey_1']).call()).to.has.length(0);1290 }));1291 });12921293 describe('Nesting', () => {1294 itEth('NFT: allows an Owner to nest/unnest their token', async ({helper}) => {1295 const owner = await helper.eth.createAccountWithBalance(donor);1296 const {collectionAddress, collectionId} = await helper.eth.createCollection(1297 owner,1298 {1299 ...CREATE_COLLECTION_DATA_DEFAULTS,1300 name: 'A',1301 description: 'B',1302 tokenPrefix: 'C',1303 collectionMode: 'nft',1304 nestingSettings: {token_owner: true, collection_admin: false, restricted: []},1305 },1306 ).send();13071308 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);13091310 // Create a token to be nested to1311 const mintingTargetNFTTokenIdResult = await contract.methods.mint(owner).send({from: owner});1312 const targetNFTTokenId = mintingTargetNFTTokenIdResult.events.Transfer.returnValues.tokenId;1313 const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetNFTTokenId);13141315 // Create a nested token1316 const mintingFirstTokenIdResult = await contract.methods.mint(targetNftTokenAddress).send({from: owner});1317 const firstTokenId = mintingFirstTokenIdResult.events.Transfer.returnValues.tokenId;1318 expect(await contract.methods.ownerOf(firstTokenId).call()).to.be.equal(targetNftTokenAddress);13191320 // Create a token to be nested and nest1321 const mintingSecondTokenIdResult = await contract.methods.mint(owner).send({from: owner});1322 const secondTokenId = mintingSecondTokenIdResult.events.Transfer.returnValues.tokenId;13231324 await contract.methods.transfer(targetNftTokenAddress, secondTokenId).send({from: owner});1325 expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(targetNftTokenAddress);13261327 // Unnest token back1328 await contract.methods.transferFrom(targetNftTokenAddress, owner, secondTokenId).send({from: owner});1329 expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(owner);1330 });13311332 itEth('NFT: collectionNesting()', async ({helper}) => {1333 const owner = await helper.eth.createAccountWithBalance(donor);1334 const {collectionAddress: unnestedCollectionAddress} = await helper.eth.createCollection(1335 owner,1336 new CreateCollectionData('A', 'B', 'C', 'nft'),1337 ).send();13381339 const unnestedContract = helper.ethNativeContract.collection(unnestedCollectionAddress, 'nft', owner);1340 expect(await unnestedContract.methods.collectionNesting().call({from: owner})).to.be.like([false, false, []]);13411342 const {collectionAddress} = await helper.eth.createCollection(1343 owner,1344 {1345 ...CREATE_COLLECTION_DATA_DEFAULTS,1346 name: 'A',1347 description: 'B',1348 tokenPrefix: 'C',1349 collectionMode: 'nft',1350 nestingSettings: {token_owner: true, collection_admin: false, restricted: [unnestedCollectionAddress.toString()]},1351 },1352 ).send();13531354 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);1355 expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([true, false, [unnestedCollectionAddress.toString()]]);1356 await contract.methods.setCollectionNesting([false, false, []]).send({from: owner});1357 expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([false, false, []]);1358 });13591360 itEth('NFT: disallows to nest token if nesting is disabled', async ({helper}) => {1361 const owner = await helper.eth.createAccountWithBalance(donor);13621363 const {collectionId, collectionAddress} = await helper.eth.createCollection(1364 owner,1365 {1366 ...CREATE_COLLECTION_DATA_DEFAULTS,1367 name: 'A',1368 description: 'B',1369 tokenPrefix: 'C',1370 collectionMode: 'nft',1371 nestingSettings: {token_owner: false, collection_admin: false, restricted: []},1372 },1373 ).send();13741375 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);13761377 // Create a token to nest into1378 const mintingTargetTokenIdResult = await contract.methods.mint(owner).send({from: owner});1379 const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;1380 const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetTokenId);13811382 // Create a token to nest1383 const mintingNftTokenIdResult = await contract.methods.mint(owner).send({from: owner});1384 const nftTokenId = mintingNftTokenIdResult.events.Transfer.returnValues.tokenId;13851386 // Try to nest1387 await expect(contract.methods1388 .transfer(targetNftTokenAddress, nftTokenId)1389 .call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest');1390 });1391 });13921393 describe('Flags', () => {1394 const createCollectionData = {1395 ...CREATE_COLLECTION_DATA_DEFAULTS,1396 name: 'A',1397 description: 'B',1398 tokenPrefix: 'C',1399 collectionMode: 'nft' as TCollectionMode,1400 };14011402 itEth('NFT: use numbers for flags', async ({helper}) => {1403 const owner = await helper.eth.createAccountWithBalance(donor);14041405 {1406 const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: 0}).send();1407 expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: false});1408 }14091410 {1411 const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: 64}).send();1412 expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: true});1413 }1414 });14151416 itEth('NFT: can\'t set foreign flag number', async ({helper}) => {1417 const owner = await helper.eth.createAccountWithBalance(donor);14181419 {1420 await expect(helper.eth.createCollection(owner, {...createCollectionData, flags: 128}).call({from: owner})).to.be.rejectedWith(/internal flags were used/);1421 }14221423 {1424 await expect(helper.eth.createCollection(owner, {...createCollectionData, flags: 192}).call({from: owner})).to.be.rejectedWith(/internal flags were used/);1425 }1426 });14271428 itEth('NFT: use enum for flags', async ({helper}) => {1429 const owner = await helper.eth.createAccountWithBalance(donor);14301431 {1432 const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: [CollectionFlag.Erc721metadata]}).send();1433 expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: true});1434 }1435 });14361437 itEth('NFT: foreign flag enum is ignored', async ({helper}) => {1438 const owner = await helper.eth.createAccountWithBalance(donor);14391440 {1441 await expect(helper.eth.createCollection(owner, {...createCollectionData, flags: [CollectionFlag.Foreign]}).call({from: owner})).to.be.rejectedWith(/internal flags were used/);1442 }14431444 {1445 await expect(helper.eth.createCollection(owner, {...createCollectionData, flags: [CollectionFlag.Erc721metadata | CollectionFlag.Foreign]}).call({from: owner})).to.be.rejectedWith(/internal flags were used/);1446 }1447 });1448 });1449});