difftreelog
test forbid creating ApiPromise without set endpoint
in: master
4 files changed
tests/src/.outdated/substrate/substrate-api.tsdiffbeforeafterboth--- a/tests/src/.outdated/substrate/substrate-api.ts
+++ b/tests/src/.outdated/substrate/substrate-api.ts
@@ -71,6 +71,7 @@
export async function getApiConnection(settings: ApiOptions | undefined = undefined): Promise<ApiPromise> {
settings = settings || defaultApiOptions();
+ if(!settings.provider) throw new Error('provider was not set');
const api = new ApiPromise(settings);
if (api) {
tests/src/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 {IKeyringPair} from '@polkadot/types/types';18import {evmToAddress} from '@polkadot/util-crypto';19import {Pallets, requirePalletsOrSkip} from '../util';20import {expect, itEth, usingEthPlaygrounds} from './util';21import {CREATE_COLLECTION_DATA_DEFAULTS, CollectionLimitField, CollectionMode, CreateCollectionData, TokenPermissionField, emptyAddress} from './util/playgrounds/types';22import {CollectionFlag, IEthCrossAccountId, TCollectionMode} from '../util/playgrounds/types';2324const DECIMALS = 18;25const CREATE_COLLECTION_DATA_DEFAULTS_ARRAY = [26 [], // properties27 [], // tokenPropertyPermissions28 [], // adminList29 [false, false, []], // nestingSettings30 [], // limits31 emptyAddress, // pendingSponsor32 [0], // flags33];3435type ElementOf<A> = A extends readonly (infer T)[] ? T : never;36function* cartesian<T extends Array<Array<any>>, R extends Array<any>>(internalRest: [...R], ...args: [...T]): Generator<[...R, ...{[K in keyof T]: ElementOf<T[K]>}]> {37 if(args.length === 0) {38 yield internalRest as any;39 return;40 }41 for(const value of args[0]) {42 yield* cartesian([...internalRest, value], ...args.slice(1)) as any;43 }44}4546describe('Create collection from EVM', () => {47 let donor: IKeyringPair;48 let nominal: bigint;4950 before(async function() {51 await usingEthPlaygrounds(async (helper, privateKey) => {52 donor = await privateKey({url: import.meta.url});53 nominal = helper.balance.getOneTokenNominal();54 });55 });5657 describe('Fungible collection', () => {58 before(async function() {59 await usingEthPlaygrounds((helper) => {60 requirePalletsOrSkip(this, helper, [Pallets.Fungible]);61 return Promise.resolve();62 });63 });6465 itEth('Collection address exist', async ({helper}) => {66 const owner = await helper.eth.createAccountWithBalance(donor);67 const collectionAddressForNonexistentCollection = '0x17C4E6453CC49AAAAEACA894E6D9683E00112233';68 const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);6970 expect(await collectionHelpers71 .methods.isCollectionExist(collectionAddressForNonexistentCollection).call())72 .to.be.false;7374 const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Exister', 'absolutely anything', 'WIWT', 'ft', DECIMALS)).send();75 expect(await collectionHelpers76 .methods.isCollectionExist(collectionAddress).call())77 .to.be.true;7879 // check collectionOwner:80 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true);81 const collectionOwner = await collectionEvm.methods.collectionOwner().call();82 expect(helper.address.restoreCrossAccountFromBigInt(BigInt(collectionOwner.sub))).to.eq(helper.address.ethToSubstrate(owner, true));83 });8485 itEth('destroyCollection', async ({helper}) => {86 const owner = await helper.eth.createAccountWithBalance(donor);87 const {collectionAddress, collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData('Exister', 'absolutely anything', 'WIWT', 'ft', DECIMALS)).send();88 const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);8990 const result = await collectionHelper.methods91 .destroyCollection(collectionAddress)92 .send({from: owner});9394 const events = helper.eth.normalizeEvents(result.events);9596 expect(events).to.be.deep.equal([97 {98 address: collectionHelper.options.address,99 event: 'CollectionDestroyed',100 args: {101 collectionId: collectionAddress,102 },103 },104 ]);105106 expect(await collectionHelper.methods107 .isCollectionExist(collectionAddress)108 .call()).to.be.false;109 expect(await helper.collection.getData(collectionId)).to.be.null;110 });111112 itEth('(!negative test!) Create collection (bad lengths)', async ({helper}) => {113 const owner = await helper.eth.createAccountWithBalance(donor);114 const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);115 {116 const MAX_NAME_LENGTH = 64;117 const collectionName = 'A'.repeat(MAX_NAME_LENGTH + 1);118 const description = 'A';119 const tokenPrefix = 'A';120121 await expect(collectionHelper.methods122 .createCollection([123 collectionName,124 description,125 tokenPrefix,126 CollectionMode.Fungible,127 DECIMALS,128 ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,129 ])130 .call({value: Number(2n * nominal)})).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGTH);131 }132 {133 const MAX_DESCRIPTION_LENGTH = 256;134 const collectionName = 'A';135 const description = 'A'.repeat(MAX_DESCRIPTION_LENGTH + 1);136 const tokenPrefix = 'A';137 await expect(collectionHelper.methods138 .createCollection([139 collectionName,140 description,141 tokenPrefix,142 CollectionMode.Fungible,143 DECIMALS,144 ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,145 ])146 .call({value: Number(2n * nominal)})).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGTH);147 }148 {149 const MAX_TOKEN_PREFIX_LENGTH = 16;150 const collectionName = 'A';151 const description = 'A';152 const tokenPrefix = 'A'.repeat(MAX_TOKEN_PREFIX_LENGTH + 1);153 await expect(collectionHelper.methods154 .createCollection([155 collectionName,156 description,157 tokenPrefix,158 CollectionMode.Fungible,159 DECIMALS,160 ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,161 ])162 .call({value: Number(2n * nominal)})).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGTH);163 }164 });165166 itEth('(!negative test!) cannot create collection if value !== 2', async ({helper}) => {167 const owner = await helper.eth.createAccountWithBalance(donor);168 const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);169 const expects = [0n, 1n, 30n].map(async value => {170 await expect(collectionHelper.methods171 .createCollection([172 'Peasantry',173 'absolutely anything',174 'TWIW',175 CollectionMode.Fungible,176 DECIMALS,177 ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,178 ])179 .call({value: Number(value * nominal)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');180 });181 await Promise.all(expects);182 });183 });184185 describe('Nonfungible collection', () => {186 before(async function() {187 await usingEthPlaygrounds((helper) => {188 requirePalletsOrSkip(this, helper, [Pallets.NFT]);189 return Promise.resolve();190 });191 });192193 itEth('Create collection', async ({helper}) => {194 const owner = await helper.eth.createAccountWithBalance(donor);195196 const name = 'CollectionEVM';197 const description = 'Some description';198 const prefix = 'token prefix';199200 // todo:playgrounds this might fail when in async environment.201 const collectionCountBefore = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;202 const {collectionId, collectionAddress, events} = await helper.eth.createCollection(owner, new CreateCollectionData(name, description, prefix, 'nft')).send();203204 expect(events).to.be.deep.equal([205 {206 address: '0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F',207 event: 'CollectionCreated',208 args: {209 owner: owner,210 collectionId: collectionAddress,211 },212 },213 ]);214215 const collectionCountAfter = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;216217 const collection = helper.nft.getCollectionObject(collectionId);218 const data = (await collection.getData())!;219220 expect(collectionCountAfter - collectionCountBefore).to.be.eq(1);221 expect(collectionId).to.be.eq(collectionCountAfter);222 expect(data.name).to.be.eq(name);223 expect(data.description).to.be.eq(description);224 expect(data.raw.tokenPrefix).to.be.eq(prefix);225 expect(data.raw.mode).to.be.eq('NFT');226227 const options = await collection.getOptions();228229 expect(options.tokenPropertyPermissions).to.be.empty;230 });231232 // this test will occasionally fail when in async environment.233 itEth('Check collection address exist', async ({helper}) => {234 const owner = await helper.eth.createAccountWithBalance(donor);235236 const expectedCollectionId = +(await helper.callRpc('api.rpc.unique.collectionStats')).created + 1;237 const expectedCollectionAddress = helper.ethAddress.fromCollectionId(expectedCollectionId);238 const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);239240 expect(await collectionHelpers.methods241 .isCollectionExist(expectedCollectionAddress)242 .call()).to.be.false;243244 await collectionHelpers.methods245 .createCollection([246 'A',247 'A',248 'A',249 CollectionMode.Nonfungible,250 0,251 ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,252 ])253 .send({value: Number(2n * helper.balance.getOneTokenNominal())});254255 expect(await collectionHelpers.methods256 .isCollectionExist(expectedCollectionAddress)257 .call()).to.be.true;258 });259 });260261 describe('Create RFT collection from EVM', () => {262 before(async function() {263 await usingEthPlaygrounds((helper) => {264 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);265 return Promise.resolve();266 });267 });268269 itEth('Create collection', async ({helper}) => {270 const owner = await helper.eth.createAccountWithBalance(donor);271272 const name = 'CollectionEVM';273 const description = 'Some description';274 const prefix = 'token prefix';275276 const {collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData(name, description, prefix, 'rft')).send();277 const data = (await helper.rft.getData(collectionId))!;278 const collection = helper.rft.getCollectionObject(collectionId);279280 expect(data.name).to.be.eq(name);281 expect(data.description).to.be.eq(description);282 expect(data.raw.tokenPrefix).to.be.eq(prefix);283 expect(data.raw.mode).to.be.eq('ReFungible');284285 const options = await collection.getOptions();286287 expect(options.tokenPropertyPermissions).to.be.empty;288 });289290 itEth('Create collection with properties & get description', async ({helper}) => {291 const owner = await helper.eth.createAccountWithBalance(donor);292293 const name = 'CollectionEVM';294 const description = 'Some description';295 const prefix = 'token prefix';296 const baseUri = 'BaseURI';297298 const {collectionId, collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, name, description, prefix, baseUri);299 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');300301 const collection = helper.rft.getCollectionObject(collectionId);302 const data = (await collection.getData())!;303304 expect(data.name).to.be.eq(name);305 expect(data.description).to.be.eq(description);306 expect(data.raw.tokenPrefix).to.be.eq(prefix);307 expect(data.raw.mode).to.be.eq('ReFungible');308309 expect(await contract.methods.description().call()).to.deep.equal(description);310311 const options = await collection.getOptions();312 expect(options.tokenPropertyPermissions).to.be.deep.equal([313 {314 key: 'URI',315 permission: {mutable: true, collectionAdmin: true, tokenOwner: false},316 },317 {318 key: 'URISuffix',319 permission: {mutable: true, collectionAdmin: true, tokenOwner: false},320 },321 ]);322 });323324 itEth('Set sponsorship', async ({helper}) => {325 const owner = await helper.eth.createAccountWithBalance(donor);326 const sponsor = await helper.eth.createAccountWithBalance(donor);327 const ss58Format = helper.chain.getChainProperties().ss58Format;328 const {collectionId, collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Sponsor', 'absolutely anything', 'ENVY', 'rft')).send();329330 const collection = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);331 const sponsorCross = helper.ethCrossAccount.fromAddress(sponsor);332 await collection.methods.setCollectionSponsorCross(sponsorCross).send();333334 let data = (await helper.rft.getData(collectionId))!;335 expect(data.raw.sponsorship.Unconfirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format)));336337 await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');338339 const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor);340 await sponsorCollection.methods.confirmCollectionSponsorship().send();341342 data = (await helper.rft.getData(collectionId))!;343 expect(data.raw.sponsorship.Confirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format)));344 });345346 itEth('Collection address exist', async ({helper}) => {347 const owner = await helper.eth.createAccountWithBalance(donor);348 const collectionAddressForNonexistentCollection = '0x17C4E6453CC49AAAAEACA894E6D9683E00112233';349 const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);350351 expect(await collectionHelpers352 .methods.isCollectionExist(collectionAddressForNonexistentCollection).call())353 .to.be.false;354355 const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Exister', 'absolutely anything', 'WIWT', 'rft')).send();356 expect(await collectionHelpers357 .methods.isCollectionExist(collectionAddress).call())358 .to.be.true;359360 // check collectionOwner:361 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true);362 const collectionOwner = await collectionEvm.methods.collectionOwner().call();363 expect(helper.address.restoreCrossAccountFromBigInt(BigInt(collectionOwner.sub))).to.eq(helper.address.ethToSubstrate(owner, true));364 });365366 itEth('destroyCollection', async ({helper}) => {367 const owner = await helper.eth.createAccountWithBalance(donor);368 const {collectionAddress, collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData('Limits', 'absolutely anything', 'OLF', 'rft')).send();369 const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);370371 await expect(collectionHelper.methods372 .destroyCollection(collectionAddress)373 .send({from: owner})).to.be.fulfilled;374375 expect(await collectionHelper.methods376 .isCollectionExist(collectionAddress)377 .call()).to.be.false;378 expect(await helper.collection.getData(collectionId)).to.be.null;379 });380381 itEth('(!negative test!) Create collection (bad lengths)', async ({helper}) => {382 const owner = await helper.eth.createAccountWithBalance(donor);383 const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);384 {385 const MAX_NAME_LENGTH = 64;386 const collectionName = 'A'.repeat(MAX_NAME_LENGTH + 1);387 const description = 'A';388 const tokenPrefix = 'A';389390 await expect(collectionHelper.methods391 .createCollection([392 collectionName,393 description,394 tokenPrefix,395 CollectionMode.Refungible,396 DECIMALS,397 ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,398 ])399 .call({value: Number(2n * nominal)})).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGTH);400 }401 {402 const MAX_DESCRIPTION_LENGTH = 256;403 const collectionName = 'A';404 const description = 'A'.repeat(MAX_DESCRIPTION_LENGTH + 1);405 const tokenPrefix = 'A';406 await expect(collectionHelper.methods407 .createCollection([408 collectionName,409 description,410 tokenPrefix,411 CollectionMode.Refungible,412 DECIMALS,413 ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,414 ])415 .call({value: Number(2n * nominal)})).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGTH);416 }417 {418 const MAX_TOKEN_PREFIX_LENGTH = 16;419 const collectionName = 'A';420 const description = 'A';421 const tokenPrefix = 'A'.repeat(MAX_TOKEN_PREFIX_LENGTH + 1);422 await expect(collectionHelper.methods423 .createCollection([424 collectionName,425 description,426 tokenPrefix,427 CollectionMode.Refungible,428 DECIMALS,429 ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,430 ])431 .call({value: Number(2n * nominal)})).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGTH);432 }433 });434435 itEth('(!negative test!) Create collection (no funds)', async ({helper}) => {436 const owner = await helper.eth.createAccountWithBalance(donor);437 const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);438 await expect(collectionHelper.methods439 .createCollection([440 'Peasantry',441 'absolutely anything',442 'TWIW',443 CollectionMode.Refungible,444 0,445 ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,446 ])447 .call({value: Number(1n * nominal)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');448 });449 });450451 describe('Sponsoring', () => {452 itEth('Сan remove collection sponsor', async ({helper}) => {453 const owner = await helper.eth.createAccountWithBalance(donor);454 const sponsor = await helper.eth.createAccountWithBalance(donor);455 const sponsorCross = helper.ethCrossAccount.fromAddr(sponsor);456457 const {collectionAddress} = await helper.eth.createCollection(458 owner,459 {460 ...CREATE_COLLECTION_DATA_DEFAULTS,461 name: 'Sponsor collection',462 description: '1',463 tokenPrefix: '1',464 collectionMode: 'nft',465 pendingSponsor: sponsorCross,466 },467 ).send();468 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);469470 expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.true;471472 await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});473 let sponsorStruct = await collectionEvm.methods.collectionSponsor().call({from: owner});474 expect(helper.address.restoreCrossAccountFromBigInt(BigInt(sponsorStruct.sub))).to.be.eq(helper.address.ethToSubstrate(sponsor, true));475 expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false;476477 await collectionEvm.methods.removeCollectionSponsor().send({from: owner});478479 sponsorStruct = await collectionEvm.methods.collectionSponsor().call({from: owner});480 expect(sponsorStruct.eth).to.be.eq('0x0000000000000000000000000000000000000000');481 });482483 itEth('Can sponsor from evm address via access list', async ({helper}) => {484 const owner = await helper.eth.createAccountWithBalance(donor);485 const sponsorEth = await helper.eth.createAccountWithBalance(donor);486 const sponsorCross = helper.ethCrossAccount.fromAddr(sponsorEth);487488 const {collectionId, collectionAddress} = await helper.eth.createERC721MetadataCompatibleCollection(489 owner,490 {491 ...CREATE_COLLECTION_DATA_DEFAULTS,492 name: 'Sponsor collection',493 description: '1',494 tokenPrefix: '1',495 collectionMode: 'nft',496 pendingSponsor: sponsorCross,497 limits: [{field: CollectionLimitField.SponsoredDataRateLimit, value: 30n}],498 tokenPropertyPermissions: [{key: 'key', permissions: [{code: TokenPermissionField.TokenOwner, value: true}]}],499 },500 '',501 );502503 const collectionSub = helper.nft.getCollectionObject(collectionId);504 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);505506 let sponsorship = (await collectionSub.getData())!.raw.sponsorship;507 expect(sponsorship.Unconfirmed).to.be.eq(helper.address.ethToSubstrate(sponsorEth, true));508 // Account cannot confirm sponsorship if it is not set as a sponsor509 await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');510511 // Sponsor can confirm sponsorship:512 await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsorEth});513 sponsorship = (await collectionSub.getData())!.raw.sponsorship;514 expect(sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsorEth, true));515516 // Create user with no balance:517 const user = helper.ethCrossAccount.createAccount();518 const nextTokenId = await collectionEvm.methods.nextTokenId().call();519 expect(nextTokenId).to.be.equal('1');520521 // Set collection permissions:522 const oldPermissions = (await collectionSub.getData())!.raw.permissions;523 expect(oldPermissions.mintMode).to.be.false;524 expect(oldPermissions.access).to.be.equal('Normal');525526 await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner});527 await collectionEvm.methods.addToCollectionAllowListCross(user).send({from: owner});528 await collectionEvm.methods.setCollectionMintMode(true).send({from: owner});529530 const newPermissions = (await collectionSub.getData())!.raw.permissions;531 expect(newPermissions.mintMode).to.be.true;532 expect(newPermissions.access).to.be.equal('AllowList');533534 const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));535 const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth));536 const userBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user.eth));537538 // User can mint token without balance:539 {540 const result = await collectionEvm.methods.mintCross(user, [{key: 'key', value: Buffer.from('Value')}]).send({from: user.eth});541 const event = helper.eth.normalizeEvents(result.events)542 .find(event => event.event === 'Transfer');543544 expect(event).to.be.deep.equal({545 address: collectionAddress,546 event: 'Transfer',547 args: {548 from: '0x0000000000000000000000000000000000000000',549 to: user.eth,550 tokenId: '1',551 },552 });553554 // await collectionEvm.methods.setProperties(1, [{key: 'key', value: Buffer.from('Value1')}]).send({from: user.eth});555556 const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));557 const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth));558 const userBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user.eth));559560 expect(await collectionEvm.methods.properties(nextTokenId, []).call())561 .to.be.like([562 [563 'key',564 '0x' + Buffer.from('Value').toString('hex'),565 ],566 ]);567 expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter);568 expect(userBalanceAfter).to.be.eq(userBalanceBefore);569 expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true;570 }571 });572573 itEth('Check that transaction via EVM spend money from sponsor address', async ({helper}) => {574 const owner = await helper.eth.createAccountWithBalance(donor);575 const sponsor = await helper.eth.createAccountWithBalance(donor);576 const sponsorCross = helper.ethCrossAccount.fromAddr(sponsor);577 const user = helper.eth.createAccount();578 const userCross = helper.ethCrossAccount.fromAddress(user);579580 const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleCollection(581 owner,582 {583 ...CREATE_COLLECTION_DATA_DEFAULTS,584 name: 'Sponsor collection',585 description: '1',586 tokenPrefix: '1',587 collectionMode: 'nft',588 pendingSponsor: sponsorCross,589 adminList: [userCross],590 },591 '',592 );593594 const collectionSub = helper.nft.getCollectionObject(collectionId);595 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);596 // Set collection sponsor:597 let collectionData = (await collectionSub.getData())!;598 expect(collectionData.raw.sponsorship.Unconfirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true));599 await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');600601 await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});602 collectionData = (await collectionSub.getData())!;603 expect(collectionData.raw.sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true));604605 const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));606 const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));607608 const mintingResult = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user});609 const tokenId = mintingResult.events.Transfer.returnValues.tokenId;610611 const event = helper.eth.normalizeEvents(mintingResult.events)612 .find(event => event.event === 'Transfer');613 const address = helper.ethAddress.fromCollectionId(collectionId);614615 expect(event).to.be.deep.equal({616 address,617 event: 'Transfer',618 args: {619 from: '0x0000000000000000000000000000000000000000',620 to: user,621 tokenId: '1',622 },623 });624 expect(await collectionEvm.methods.tokenURI(tokenId).call({from: user})).to.be.equal('Test URI');625626 const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));627 expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);628 const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));629 expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;630 });631632 itEth('Can reassign collection sponsor', async ({helper}) => {633 const owner = await helper.eth.createAccountWithBalance(donor);634 const sponsorEth = await helper.eth.createAccountWithBalance(donor);635 const sponsorCrossEth = helper.ethCrossAccount.fromAddr(sponsorEth);636 const [sponsorSub] = await helper.arrange.createAccounts([100n], donor);637 const sponsorCrossSub = helper.ethCrossAccount.fromKeyringPair(sponsorSub);638639 const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleCollection(640 owner,641 {642 ...CREATE_COLLECTION_DATA_DEFAULTS,643 name: 'Sponsor collection',644 description: '1',645 tokenPrefix: '1',646 collectionMode: 'nft',647 pendingSponsor: sponsorCrossEth,648 },649 '',650 );651 const collectionSub = helper.nft.getCollectionObject(collectionId);652 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);653654 // Set and confirm sponsor:655 await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsorEth});656657 // Can reassign sponsor:658 await collectionEvm.methods.setCollectionSponsorCross(sponsorCrossSub).send({from: owner});659 const collectionSponsor = (await collectionSub.getData())?.raw.sponsorship;660 expect(collectionSponsor).to.deep.eq({Unconfirmed: sponsorSub.address});661 });662663 [664 'transfer',665 'transferCross',666 'transferFrom',667 'transferFromCross',668 ].map(testCase =>669 itEth(`[${testCase}] Check that transfer via EVM spend money from sponsor address`, async ({helper}) => {670 const owner = await helper.eth.createAccountWithBalance(donor);671 const sponsor = await helper.eth.createAccountWithBalance(donor);672 const sponsorCross = helper.ethCrossAccount.fromAddr(sponsor);673 const user = await helper.eth.createAccountWithBalance(donor);674 const userCross = helper.ethCrossAccount.fromAddress(user);675676 const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleCollection(677 owner,678 {679 ...CREATE_COLLECTION_DATA_DEFAULTS,680 name: 'Sponsor collection',681 description: '1',682 tokenPrefix: '1',683 collectionMode: 'rft',684 pendingSponsor: sponsorCross,685 adminList: [userCross],686 },687 '',688 );689 const receiver = await helper.eth.createAccountWithBalance(donor);690 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);691692 await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});693694 const result = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user});695 const tokenId = result.events.Transfer.returnValues.tokenId;696697 const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));698 const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));699 const userBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));700701 switch (testCase) {702 case 'transfer':703 await collectionEvm.methods.transfer(receiver, tokenId).send({from: user});704 break;705 case 'transferCross':706 await collectionEvm.methods.transferCross(helper.ethCrossAccount.fromAddress(receiver), tokenId).send({from: user});707 break;708 case 'transferFrom':709 await collectionEvm.methods.transferFrom(user, receiver, tokenId).send({from: user});710 break;711 case 'transferFromCross':712 await collectionEvm.methods.transferFromCross(helper.ethCrossAccount.fromAddress(user), helper.ethCrossAccount.fromAddress(receiver), tokenId).send({from: user});713 break;714 }715716 const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));717 expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);718 const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));719 expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;720 const userBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));721 expect(userBalanceAfter).to.be.eq(userBalanceBefore);722 }));723 });724725 describe('Collection admins', () => {726 let donor: IKeyringPair;727728 before(async function() {729 await usingEthPlaygrounds(async (_helper, privateKey) => {730 donor = await privateKey({url: import.meta.url});731 });732 });733734 [735 {mode: 'nft' as const, requiredPallets: []},736 {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},737 {mode: 'ft' as const, requiredPallets: []},738 ].map(testCase => {739 itEth.ifWithPallets(`can add account admin by owner for ${testCase.mode}`, testCase.requiredPallets, async ({helper, privateKey}) => {740 // arrange741 const owner = await helper.eth.createAccountWithBalance(donor);742 const adminSub = await privateKey('//admin2');743 const adminEth = helper.eth.createAccount().toLowerCase();744745 const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub);746 const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth);747748 const {collectionAddress, collectionId} = await helper.eth.createCollection(749 owner,750 {751 ...CREATE_COLLECTION_DATA_DEFAULTS,752 name: 'Mint collection',753 description: 'a',754 tokenPrefix: 'b',755 collectionMode: testCase.mode,756 adminList: [adminCrossSub, adminCrossEth],757 },758 ).send();759 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner, true);760761 // 1. Expect api.rpc.unique.adminlist returns admins:762 const adminListRpc = await helper.collection.getAdmins(collectionId);763 expect(adminListRpc).to.has.length(2);764 expect(adminListRpc).to.be.deep.contain.members([{Substrate: adminSub.address}, {Ethereum: adminEth}]);765766 // 2. Expect methods.collectionAdmins == api.rpc.unique.adminlist767 let adminListEth = await collectionEvm.methods.collectionAdmins().call();768 adminListEth = adminListEth.map((element: IEthCrossAccountId) => helper.address.convertCrossAccountFromEthCrossAccount(element));769 expect(adminListRpc).to.be.like(adminListEth);770771 // 3. check isOwnerOrAdminCross returns true:772 expect(await collectionEvm.methods.isOwnerOrAdminCross(adminCrossSub).call()).to.be.true;773 expect(await collectionEvm.methods.isOwnerOrAdminCross(adminCrossEth).call()).to.be.true;774 });775 });776777 itEth('cross account admin can mint', async ({helper}) => {778 // arrange: create collection and accounts779 const owner = await helper.eth.createAccountWithBalance(donor);780 const adminEth = (await helper.eth.createAccountWithBalance(donor)).toLowerCase();781 const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth);782 const [adminSub] = await helper.arrange.createAccounts([100n], donor);783 const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub);784 const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleCollection(785 owner,786 {787 ...CREATE_COLLECTION_DATA_DEFAULTS,788 name: 'Mint collection',789 description: 'a',790 tokenPrefix: 'b',791 collectionMode: 'nft',792 adminList: [adminCrossSub, adminCrossEth],793 },794 'uri',795 );796 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner, true);797798 // admin (sub and eth) can mint token:799 await collectionEvm.methods.mint(owner).send({from: adminEth});800 await helper.nft.mintToken(adminSub, {collectionId, owner: {Ethereum: owner}});801802 expect(await helper.collection.getLastTokenId(collectionId)).to.eq(2);803 });804805 itEth('cannot add invalid cross account admin', async ({helper}) => {806 const owner = await helper.eth.createAccountWithBalance(donor);807 const [admin] = await helper.arrange.createAccounts([100n, 100n], donor);808809 const adminCross = {810 eth: helper.address.substrateToEth(admin.address),811 sub: admin.addressRaw,812 };813814 await expect(helper.eth.createCollection(815 owner,816 {817 ...CREATE_COLLECTION_DATA_DEFAULTS,818 name: 'A',819 description: 'B',820 tokenPrefix: 'C',821 collectionMode: 'nft',822 adminList: [adminCross],823 },824 ).call()).to.be.rejected;825 });826827 itEth('Remove [cross] admin by owner', async ({helper}) => {828 const owner = await helper.eth.createAccountWithBalance(donor);829830 const [adminSub] = await helper.arrange.createAccounts([10n], donor);831 const adminEth = (await helper.eth.createAccountWithBalance(donor)).toLowerCase();832 const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub);833 const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth);834835 const {collectionAddress, collectionId} = await helper.eth.createCollection(836 owner,837 {838 ...CREATE_COLLECTION_DATA_DEFAULTS,839 name: 'A',840 description: 'B',841 tokenPrefix: 'C',842 collectionMode: 'nft',843 adminList: [adminCrossSub, adminCrossEth],844 },845 ).send();846847 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);848849 {850 const adminList = await helper.collection.getAdmins(collectionId);851 expect(adminList).to.deep.include({Substrate: adminSub.address});852 expect(adminList).to.deep.include({Ethereum: adminEth});853 }854855 await collectionEvm.methods.removeCollectionAdminCross(adminCrossSub).send();856 await collectionEvm.methods.removeCollectionAdminCross(adminCrossEth).send();857 const adminList = await helper.collection.getAdmins(collectionId);858 expect(adminList.length).to.be.eq(0);859860 // Non admin cannot mint:861 await expect(helper.nft.mintToken(adminSub, {collectionId, owner: {Substrate: adminSub.address}})).to.be.rejectedWith(/common.PublicMintingNotAllowed/);862 await expect(collectionEvm.methods.mint(adminEth).send({from: adminEth})).to.be.rejected;863 });864 });865866 describe('Collection limits', () => {867 describe('Can set collection limits', () => {868 [869 {case: 'nft' as const},870 {case: 'rft' as const, requiredPallets: [Pallets.ReFungible]},871 {case: 'ft' as const},872 ].map(testCase =>873 itEth.ifWithPallets(`for ${testCase.case}`, testCase.requiredPallets || [], async ({helper}) => {874 const owner = await helper.eth.createAccountWithBalance(donor);875876 const limits = {877 accountTokenOwnershipLimit: 1000n,878 sponsoredDataSize: 1024n,879 sponsoredDataRateLimit: 30n,880 tokenLimit: 1000000n,881 sponsorTransferTimeout: 6n,882 sponsorApproveTimeout: 6n,883 ownerCanTransfer: 1n,884 ownerCanDestroy: 0n,885 transfersEnabled: 0n,886 };887888 const {collectionId, collectionAddress} = await helper.eth.createCollection(889 owner,890 {891 ...CREATE_COLLECTION_DATA_DEFAULTS,892 name: 'Limits',893 description: 'absolutely anything',894 tokenPrefix: 'FLO',895 collectionMode: testCase.case,896 limits: [897 {field: CollectionLimitField.AccountTokenOwnership, value: limits.accountTokenOwnershipLimit},898 {field: CollectionLimitField.SponsoredDataSize, value: limits.sponsoredDataSize},899 {field: CollectionLimitField.SponsoredDataRateLimit, value: limits.sponsoredDataRateLimit},900 {field: CollectionLimitField.TokenLimit, value: limits.tokenLimit},901 {field: CollectionLimitField.SponsorTransferTimeout, value: limits.sponsorTransferTimeout},902 {field: CollectionLimitField.SponsorApproveTimeout, value: limits.sponsorApproveTimeout},903 {field: CollectionLimitField.OwnerCanTransfer, value: limits.ownerCanTransfer},904 {field: CollectionLimitField.OwnerCanDestroy, value: limits.ownerCanDestroy},905 {field: CollectionLimitField.TransferEnabled, value: limits.transfersEnabled},906 ],907 },908 ).send();909910 const expectedLimits = {911 accountTokenOwnershipLimit: 1000,912 sponsoredDataSize: 1024,913 sponsoredDataRateLimit: {blocks: 30},914 tokenLimit: 1000000,915 sponsorTransferTimeout: 6,916 sponsorApproveTimeout: 6,917 ownerCanTransfer: true,918 ownerCanDestroy: false,919 transfersEnabled: false,920 };921922 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.case, owner);923924 // Check limits from sub:925 const data = (await helper.rft.getData(collectionId))!;926 expect(data.raw.limits).to.deep.eq(expectedLimits);927 expect(await helper.collection.getEffectiveLimits(collectionId)).to.deep.eq(expectedLimits);928 // Check limits from eth:929 const limitsEvm = await collectionEvm.methods.collectionLimits().call({from: owner});930 expect(limitsEvm).to.have.length(9);931 expect(limitsEvm[0]).to.deep.eq([CollectionLimitField.AccountTokenOwnership.toString(), [true, limits.accountTokenOwnershipLimit.toString()]]);932 expect(limitsEvm[1]).to.deep.eq([CollectionLimitField.SponsoredDataSize.toString(), [true, limits.sponsoredDataSize.toString()]]);933 expect(limitsEvm[2]).to.deep.eq([CollectionLimitField.SponsoredDataRateLimit.toString(), [true, limits.sponsoredDataRateLimit.toString()]]);934 expect(limitsEvm[3]).to.deep.eq([CollectionLimitField.TokenLimit.toString(), [true, limits.tokenLimit.toString()]]);935 expect(limitsEvm[4]).to.deep.eq([CollectionLimitField.SponsorTransferTimeout.toString(), [true, limits.sponsorTransferTimeout.toString()]]);936 expect(limitsEvm[5]).to.deep.eq([CollectionLimitField.SponsorApproveTimeout.toString(), [true, limits.sponsorApproveTimeout.toString()]]);937 expect(limitsEvm[6]).to.deep.eq([CollectionLimitField.OwnerCanTransfer.toString(), [true, limits.ownerCanTransfer.toString()]]);938 expect(limitsEvm[7]).to.deep.eq([CollectionLimitField.OwnerCanDestroy.toString(), [true, limits.ownerCanDestroy.toString()]]);939 expect(limitsEvm[8]).to.deep.eq([CollectionLimitField.TransferEnabled.toString(), [true, limits.transfersEnabled.toString()]]);940 }));941 });942943 describe('(!negative test!) Cannot set invalid collection limits', () => {944 [945 {case: 'nft' as const},946 {case: 'rft' as const, requiredPallets: [Pallets.ReFungible]},947 {case: 'ft' as const},948 ].map(testCase =>949 itEth.ifWithPallets(`for ${testCase.case}`, testCase.requiredPallets || [], async ({helper}) => {950 const invalidLimits = {951 accountTokenOwnershipLimit: BigInt(Number.MAX_SAFE_INTEGER),952 transfersEnabled: 3,953 };954955 const createCollectionData = {956 ...CREATE_COLLECTION_DATA_DEFAULTS,957 name: 'Limits',958 description: 'absolutely anything',959 tokenPrefix: 'ISNI',960 collectionMode: testCase.case,961 };962963 const owner = await helper.eth.createAccountWithBalance(donor);964 await expect(helper.eth.createCollection(965 owner,966 {967 ...createCollectionData,968 limits: [{field: 9 as CollectionLimitField, value: 1n}],969 },970 ).call()).to.be.rejectedWith('value not convertible into enum "CollectionLimitField"');971972 await expect(helper.eth.createCollection(973 owner,974 {975 ...createCollectionData,976 limits: [{field: CollectionLimitField.AccountTokenOwnership, value: invalidLimits.accountTokenOwnershipLimit}],977 },978 ).call()).to.be.rejectedWith(`can't convert value to u32 "${invalidLimits.accountTokenOwnershipLimit}"`);979980 await expect(helper.eth.createCollection(981 owner,982 {983 ...createCollectionData,984 limits: [{field: CollectionLimitField.TransferEnabled, value: 3n}],985 },986 ).call()).to.be.rejectedWith(`can't convert value to boolean "${invalidLimits.transfersEnabled}"`);987988 await expect(helper.eth.createCollection(989 owner,990 {991 ...createCollectionData,992 limits: [{field: CollectionLimitField.SponsoredDataSize, value: -1n}],993 },994 ).call()).to.be.rejectedWith('value out-of-bounds');995 }));996 });997 });998999 describe('Collection properties', () => {10001001 [1002 {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'}]},1003 {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'}]},1004 {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'}]},1005 ].map(testCase =>1006 itEth.ifWithPallets(`Collection properties can be set for ${testCase.mode}`, testCase.mode === 'rft' ? [Pallets.ReFungible] : [], async({helper}) => {1007 const caller = await helper.eth.createAccountWithBalance(donor);1008 const callerCross = helper.ethCrossAccount.fromAddress(caller);1009 const owner = await helper.eth.createAccountWithBalance(donor);1010 const {collectionId, collectionAddress} = await helper.eth.createCollection(1011 owner,1012 {1013 ...CREATE_COLLECTION_DATA_DEFAULTS,1014 name: 'name',1015 description: 'test',1016 tokenPrefix: 'test',1017 collectionMode: testCase.mode,1018 adminList: [callerCross],1019 properties: testCase.methodParams,1020 },1021 ).send();10221023 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, caller);10241025 const raw = (await helper[testCase.mode].getData(collectionId))?.raw;1026 expect(raw.properties).to.deep.equal(testCase.expectedProps);10271028 // collectionProperties returns properties:1029 expect(await collectionEvm.methods.collectionProperties([]).call()).to.be.like(testCase.expectedProps.map(prop => helper.ethProperty.property(prop.key, prop.value)));1030 }));10311032 [1033 {mode: 'nft' as const},1034 {mode: 'rft' as const},1035 {mode: 'ft' as const},1036 ].map(testCase =>1037 itEth.ifWithPallets(`Collection properties can be deleted for ${testCase.mode}`, testCase.mode === 'rft' ? [Pallets.ReFungible] : [], async({helper}) => {1038 const caller = await helper.eth.createAccountWithBalance(donor);1039 const callerCross = helper.ethCrossAccount.fromAddress(caller);1040 const owner = await helper.eth.createAccountWithBalance(donor);1041 const {collectionId, collectionAddress} = await helper.eth.createCollection(1042 owner,1043 {1044 ...CREATE_COLLECTION_DATA_DEFAULTS,1045 name: 'name',1046 description: 'test',1047 tokenPrefix: 'test',1048 collectionMode: testCase.mode,1049 adminList: [callerCross],1050 properties:[1051 {key: 'testKey1', value: Buffer.from('testValue1')},1052 {key: 'testKey2', value: Buffer.from('testValue2')},1053 {key: 'testKey3', value: Buffer.from('testValue3')}],1054 },1055 ).send();10561057 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, testCase.mode, caller);10581059 await collectionEvm.methods.deleteCollectionProperties(['testKey1', 'testKey2']).send({from: caller});10601061 const raw = (await helper[testCase.mode].getData(collectionId))?.raw;10621063 expect(raw.properties.length).to.equal(1);1064 expect(raw.properties).to.deep.equal([{key: 'testKey3', value: 'testValue3'}]);1065 }));10661067 itEth('(!negative test!) Cannot set invalid properties', async({helper}) => {1068 const caller = await helper.eth.createAccountWithBalance(donor);1069 const callerCross = helper.ethCrossAccount.fromAddress(caller);1070 const owner = await helper.eth.createAccountWithBalance(donor);1071 const createCollectionData = {1072 ...CREATE_COLLECTION_DATA_DEFAULTS,1073 name: 'name',1074 description: 'test',1075 tokenPrefix: 'test',1076 collectionMode: 'nft' as TCollectionMode,1077 adminList: [callerCross],1078 };1079 await expect(helper.eth.createCollection(1080 owner,1081 {1082 ...createCollectionData,1083 properties: [{key: '', value: Buffer.from('val1')}],1084 },1085 ).call()).to.be.rejected;10861087 await expect(helper.eth.createCollection(1088 owner,1089 {1090 ...createCollectionData,1091 properties: [{key: 'déjà vu', value: Buffer.from('hmm...')}],1092 },1093 ).call()).to.be.rejected;10941095 await expect(helper.eth.createCollection(1096 owner,1097 {1098 ...createCollectionData,1099 properties: [{key: 'a'.repeat(257), value: Buffer.from('val3')}],1100 },1101 ).call()).to.be.rejected;1102 });11031104 itEth('(!negative test!) cannot delete properties of non-owned collections', async ({helper}) => {1105 const caller = await helper.eth.createAccountWithBalance(donor);1106 const owner = await helper.eth.createAccountWithBalance(donor);1107 const {collectionAddress} = await helper.eth.createCollection(1108 owner,1109 {1110 ...CREATE_COLLECTION_DATA_DEFAULTS,1111 name: 'name',1112 description: 'test',1113 tokenPrefix: 'test',1114 collectionMode: 'nft',1115 properties:[1116 {key: 'testKey1', value: Buffer.from('testValue1')},1117 {key: 'testKey2', value: Buffer.from('testValue2')}],1118 },1119 ).send();11201121 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller);11221123 await expect(collectionEvm.methods.deleteCollectionProperties(['testKey2']).send({from: caller})).to.be.rejected;1124 });1125 });11261127 describe('Token property permissions', () => {1128 [1129 {mode: 'nft' as const, requiredPallets: []},1130 {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},1131 ].map(testCase =>1132 itEth.ifWithPallets(`[${testCase.mode}] Can set all possible token property permissions`, testCase.requiredPallets, async({helper}) => {1133 const owner = await helper.eth.createAccountWithBalance(donor);1134 const caller = await helper.ethCrossAccount.createAccountWithBalance(donor);1135 for(const [mutable,collectionAdmin, tokenOwner] of cartesian([], [false, true], [false, true], [false, true])) {1136 const {collectionId, collectionAddress} = await helper.eth.createCollection(1137 owner,1138 {1139 ...CREATE_COLLECTION_DATA_DEFAULTS,1140 name: 'A',1141 description: 'B',1142 tokenPrefix: 'C',1143 collectionMode: testCase.mode,1144 adminList: [caller],1145 tokenPropertyPermissions: [1146 {1147 key: 'testKey',1148 permissions: [1149 {code: TokenPermissionField.Mutable, value: mutable},1150 {code: TokenPermissionField.TokenOwner, value: tokenOwner},1151 {code: TokenPermissionField.CollectionAdmin, value: collectionAdmin},1152 ],1153 },1154 ],1155 },1156 ).send();1157 const collection = helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner);11581159 expect(await helper[testCase.mode].getPropertyPermissions(collectionId)).to.be.deep.equal([{1160 key: 'testKey',1161 permission: {mutable, collectionAdmin, tokenOwner},1162 }]);11631164 expect(await collection.methods.tokenPropertyPermissions().call({from: caller.eth})).to.be.like([1165 ['testKey', [1166 [TokenPermissionField.Mutable.toString(), mutable],1167 [TokenPermissionField.TokenOwner.toString(), tokenOwner],1168 [TokenPermissionField.CollectionAdmin.toString(), collectionAdmin]],1169 ],1170 ]);1171 }1172 }));11731174 [1175 {mode: 'nft' as const, requiredPallets: []},1176 {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},1177 ].map(testCase =>1178 itEth.ifWithPallets(`[${testCase.mode}] Can set multiple token property permissions`, testCase.requiredPallets, async({helper}) => {1179 const owner = await helper.eth.createAccountWithBalance(donor);1180 const {collectionId, collectionAddress} = await helper.eth.createCollection(1181 owner,1182 {1183 ...CREATE_COLLECTION_DATA_DEFAULTS,1184 name: 'A',1185 description: 'B',1186 tokenPrefix: 'C',1187 collectionMode: testCase.mode,1188 tokenPropertyPermissions: [1189 {1190 key: 'testKey_0',1191 permissions: [1192 {code: TokenPermissionField.Mutable, value: true},1193 {code: TokenPermissionField.TokenOwner, value: true},1194 {code: TokenPermissionField.CollectionAdmin, value: true}],1195 },1196 {1197 key: 'testKey_1',1198 permissions: [1199 {code: TokenPermissionField.Mutable, value: true},1200 {code: TokenPermissionField.TokenOwner, value: false},1201 {code: TokenPermissionField.CollectionAdmin, value: true}],1202 },1203 {1204 key: 'testKey_2',1205 permissions: [1206 {code: TokenPermissionField.Mutable, value: false},1207 {code: TokenPermissionField.TokenOwner, value: true},1208 {code: TokenPermissionField.CollectionAdmin, value: false}],1209 },1210 ],1211 },1212 ).send();1213 const collection = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner);12141215 expect(await helper[testCase.mode].getPropertyPermissions(collectionId)).to.be.deep.equal([1216 {1217 key: 'testKey_0',1218 permission: {mutable: true, tokenOwner: true, collectionAdmin: true},1219 },1220 {1221 key: 'testKey_1',1222 permission: {mutable: true, tokenOwner: false, collectionAdmin: true},1223 },1224 {1225 key: 'testKey_2',1226 permission: {mutable: false, tokenOwner: true, collectionAdmin: false},1227 },1228 ]);12291230 expect(await collection.methods.tokenPropertyPermissions().call({from: owner})).to.be.like([1231 ['testKey_0', [1232 [TokenPermissionField.Mutable.toString(), true],1233 [TokenPermissionField.TokenOwner.toString(), true],1234 [TokenPermissionField.CollectionAdmin.toString(), true]],1235 ],1236 ['testKey_1', [1237 [TokenPermissionField.Mutable.toString(), true],1238 [TokenPermissionField.TokenOwner.toString(), false],1239 [TokenPermissionField.CollectionAdmin.toString(), true]],1240 ],1241 ['testKey_2', [1242 [TokenPermissionField.Mutable.toString(), false],1243 [TokenPermissionField.TokenOwner.toString(), true],1244 [TokenPermissionField.CollectionAdmin.toString(), false]],1245 ],1246 ]);1247 }));12481249 [1250 {mode: 'nft' as const, requiredPallets: []},1251 {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},1252 ].map(testCase =>1253 itEth.ifWithPallets(`Can be deleted for ${testCase.mode}`, testCase.requiredPallets, async({helper}) => {1254 const caller = await helper.eth.createAccountWithBalance(donor);1255 const receiver = await helper.ethCrossAccount.createAccountWithBalance(donor);1256 const {collectionAddress} = await helper.eth.createCollection(1257 caller,1258 {1259 ...CREATE_COLLECTION_DATA_DEFAULTS,1260 name: 'A',1261 description: 'B',1262 tokenPrefix: 'C',1263 collectionMode: testCase.mode,1264 adminList: [receiver],1265 tokenPropertyPermissions: [1266 {1267 key: 'testKey',1268 permissions: [1269 {code: TokenPermissionField.Mutable, value: true},1270 {code: TokenPermissionField.CollectionAdmin, value: true}],1271 },1272 {1273 key: 'testKey_1',1274 permissions: [1275 {code: TokenPermissionField.Mutable, value: true},1276 {code: TokenPermissionField.CollectionAdmin, value: true}],1277 },1278 ],1279 },1280 ).send();12811282 const collection = helper.ethNativeContract.collection(collectionAddress, testCase.mode, caller);1283 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;1284 expect(await collection.methods.properties(tokenId, ['testKey', 'testKey_1']).call()).to.has.length(2);12851286 await collection.methods.deleteProperties(tokenId, ['testKey', 'testKey_1']).send({from: caller});1287 expect(await collection.methods.properties(tokenId, ['testKey', 'testKey_1']).call()).to.has.length(0);1288 }));1289 });12901291 describe('Nesting', () => {1292 itEth('NFT: allows an Owner to nest/unnest their token', async ({helper}) => {1293 const owner = await helper.eth.createAccountWithBalance(donor);1294 const {collectionAddress, collectionId} = await helper.eth.createCollection(1295 owner,1296 {1297 ...CREATE_COLLECTION_DATA_DEFAULTS,1298 name: 'A',1299 description: 'B',1300 tokenPrefix: 'C',1301 collectionMode: 'nft',1302 nestingSettings: {token_owner: true, collection_admin: false, restricted: []},1303 },1304 ).send();13051306 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);13071308 // Create a token to be nested to1309 const mintingTargetNFTTokenIdResult = await contract.methods.mint(owner).send({from: owner});1310 const targetNFTTokenId = mintingTargetNFTTokenIdResult.events.Transfer.returnValues.tokenId;1311 const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetNFTTokenId);13121313 // Create a nested token1314 const mintingFirstTokenIdResult = await contract.methods.mint(targetNftTokenAddress).send({from: owner});1315 const firstTokenId = mintingFirstTokenIdResult.events.Transfer.returnValues.tokenId;1316 expect(await contract.methods.ownerOf(firstTokenId).call()).to.be.equal(targetNftTokenAddress);13171318 // Create a token to be nested and nest1319 const mintingSecondTokenIdResult = await contract.methods.mint(owner).send({from: owner});1320 const secondTokenId = mintingSecondTokenIdResult.events.Transfer.returnValues.tokenId;13211322 await contract.methods.transfer(targetNftTokenAddress, secondTokenId).send({from: owner});1323 expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(targetNftTokenAddress);13241325 // Unnest token back1326 await contract.methods.transferFrom(targetNftTokenAddress, owner, secondTokenId).send({from: owner});1327 expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(owner);1328 });13291330 itEth('NFT: collectionNesting()', async ({helper}) => {1331 const owner = await helper.eth.createAccountWithBalance(donor);1332 const {collectionAddress: unnestedCollectionAddress} = await helper.eth.createCollection(1333 owner,1334 new CreateCollectionData('A', 'B', 'C', 'nft'),1335 ).send();13361337 const unnestedContract = helper.ethNativeContract.collection(unnestedCollectionAddress, 'nft', owner);1338 expect(await unnestedContract.methods.collectionNesting().call({from: owner})).to.be.like([false, false, []]);13391340 const {collectionAddress} = await helper.eth.createCollection(1341 owner,1342 {1343 ...CREATE_COLLECTION_DATA_DEFAULTS,1344 name: 'A',1345 description: 'B',1346 tokenPrefix: 'C',1347 collectionMode: 'nft',1348 nestingSettings: {token_owner: true, collection_admin: false, restricted: [unnestedCollectionAddress.toString()]},1349 },1350 ).send();13511352 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);1353 expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([true, false, [unnestedCollectionAddress.toString()]]);1354 await contract.methods.setCollectionNesting([false, false, []]).send({from: owner});1355 expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([false, false, []]);1356 });13571358 itEth('NFT: disallows to nest token if nesting is disabled', async ({helper}) => {1359 const owner = await helper.eth.createAccountWithBalance(donor);13601361 const {collectionId, collectionAddress} = await helper.eth.createCollection(1362 owner,1363 {1364 ...CREATE_COLLECTION_DATA_DEFAULTS,1365 name: 'A',1366 description: 'B',1367 tokenPrefix: 'C',1368 collectionMode: 'nft',1369 nestingSettings: {token_owner: false, collection_admin: false, restricted: []},1370 },1371 ).send();13721373 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);13741375 // Create a token to nest into1376 const mintingTargetTokenIdResult = await contract.methods.mint(owner).send({from: owner});1377 const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;1378 const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetTokenId);13791380 // Create a token to nest1381 const mintingNftTokenIdResult = await contract.methods.mint(owner).send({from: owner});1382 const nftTokenId = mintingNftTokenIdResult.events.Transfer.returnValues.tokenId;13831384 // Try to nest1385 await expect(contract.methods1386 .transfer(targetNftTokenAddress, nftTokenId)1387 .call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest');1388 });1389 });13901391 describe('Flags', () => {1392 const createCollectionData = {1393 ...CREATE_COLLECTION_DATA_DEFAULTS,1394 name: 'A',1395 description: 'B',1396 tokenPrefix: 'C',1397 collectionMode: 'nft' as TCollectionMode,1398 };13991400 itEth('NFT: use numbers for flags', async ({helper}) => {1401 const owner = await helper.eth.createAccountWithBalance(donor);14021403 {1404 const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: 0}).send();1405 expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: false});1406 }14071408 {1409 const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: 64}).send();1410 expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: true});1411 }1412 });14131414 itEth('NFT: can\'t set foreign flag number', async ({helper}) => {1415 const owner = await helper.eth.createAccountWithBalance(donor);14161417 {1418 await expect(helper.eth.createCollection(owner, {...createCollectionData, flags: 128}).call({from: owner})).to.be.rejectedWith(/internal flags were used/);1419 }14201421 {1422 await expect(helper.eth.createCollection(owner, {...createCollectionData, flags: 192}).call({from: owner})).to.be.rejectedWith(/internal flags were used/);1423 }1424 });14251426 itEth('NFT: use enum for flags', async ({helper}) => {1427 const owner = await helper.eth.createAccountWithBalance(donor);14281429 {1430 const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: [CollectionFlag.Erc721metadata]}).send();1431 expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: true});1432 }1433 });14341435 itEth('NFT: foreign flag enum is ignored', async ({helper}) => {1436 const owner = await helper.eth.createAccountWithBalance(donor);14371438 {1439 await expect(helper.eth.createCollection(owner, {...createCollectionData, flags: [CollectionFlag.Foreign]}).call({from: owner})).to.be.rejectedWith(/internal flags were used/);1440 }14411442 {1443 await expect(helper.eth.createCollection(owner, {...createCollectionData, flags: [CollectionFlag.Erc721metadata | CollectionFlag.Foreign]}).call({from: owner})).to.be.rejectedWith(/internal flags were used/);1444 }1445 });1446 });1447});tests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/unique.dev.ts
+++ b/tests/src/util/playgrounds/unique.dev.ts
@@ -278,6 +278,7 @@
}
async connect(wsEndpoint: string, _listeners?: any): Promise<void> {
+ if(!wsEndpoint) throw new Error('wsEndpoint was not set');
const wsProvider = new WsProvider(wsEndpoint);
this.api = new ApiPromise({
provider: wsProvider,
tests/src/util/playgrounds/unique.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/unique.ts
+++ b/tests/src/util/playgrounds/unique.ts
@@ -492,6 +492,7 @@
}
static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise<TNetworks> {
+ if(!wsEndpoint) throw new Error('wsEndpoint was not set');
const api = new ApiPromise({provider: new WsProvider(wsEndpoint)});
await api.isReady;
@@ -507,6 +508,7 @@
network: TNetworks;
}> {
if(typeof network === 'undefined' || network === null) network = 'opal';
+ if(!wsEndpoint) throw new Error('wsEndpoint was not set');
const supportedRPC = {
opal: {
unique: require('@unique-nft/opal-testnet-types/definitions').unique.rpc,