12345678910111213141516import type {IKeyringPair} from '@polkadot/types/types';17import {expect} from 'chai';18import {Pallets} from '../util/index.js';19import type {IEthCrossAccountId} from '@unique/playgrounds/types.js';20import {usingEthPlaygrounds, itEth} from './util/index.js';21import {EthUniqueHelper} from './util/playgrounds/unique.dev.js';22import {CreateCollectionData} from './util/playgrounds/types.js';2324async function recordEthFee(helper: EthUniqueHelper, userAddress: string, call: () => Promise<any>) {25 const before = await helper.balance.getSubstrate(helper.address.ethToSubstrate(userAddress));26 await call();27 await helper.wait.newBlocks(1);28 const after = await helper.balance.getSubstrate(helper.address.ethToSubstrate(userAddress));2930 expect(after < before).to.be.true;3132 return before - after;33}3435describe('Add collection admins', () => {36 let donor: IKeyringPair;3738 before(async function() {39 await usingEthPlaygrounds(async (_helper, privateKey) => {40 donor = await privateKey({url: import.meta.url});41 });42 });4344 [45 {mode: 'nft' as const, requiredPallets: []},46 {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},47 {mode: 'ft' as const, requiredPallets: []},48 ].map(testCase => {49 itEth.ifWithPallets(`can add account admin by owner for ${testCase.mode}`, testCase.requiredPallets, async ({helper, privateKey}) => {50 51 const owner = await helper.eth.createAccountWithBalance(donor);52 const adminSub = await privateKey('//admin2');53 const adminEth = helper.eth.createAccount().toLowerCase();5455 const adminDeprecated = helper.eth.createAccount().toLowerCase();56 const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub);57 const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth);5859 const {collectionAddress, collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData('A', 'B', 'C', testCase.mode)).send();60 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner, true);6162 63 expect(await collectionEvm.methods.isOwnerOrAdminCross(adminCrossSub).call()).to.be.false;64 expect(await collectionEvm.methods.isOwnerOrAdminCross(adminCrossEth).call()).to.be.false;65 expect(await collectionEvm.methods.isOwnerOrAdminCross(helper.ethCrossAccount.fromAddress(adminDeprecated)).call()).to.be.false;6667 68 await collectionEvm.methods.addCollectionAdmin(adminDeprecated).send();69 70 await collectionEvm.methods.addCollectionAdminCross(adminCrossSub).send();71 await collectionEvm.methods.addCollectionAdminCross(adminCrossEth).send();7273 74 const adminListRpc = await helper.collection.getAdmins(collectionId);75 expect(adminListRpc).to.has.length(3);76 expect(adminListRpc).to.be.deep.contain.members([{Substrate: adminSub.address}, {Ethereum: adminEth}, {Ethereum: adminDeprecated}]);7778 79 let adminListEth = await collectionEvm.methods.collectionAdmins().call();80 adminListEth = adminListEth.map((element: IEthCrossAccountId) => helper.address.convertCrossAccountFromEthCrossAccount(element));81 expect(adminListRpc).to.be.like(adminListEth);8283 84 expect(await collectionEvm.methods.isOwnerOrAdminCross(adminCrossSub).call()).to.be.true;85 expect(await collectionEvm.methods.isOwnerOrAdminCross(adminCrossEth).call()).to.be.true;86 expect(await collectionEvm.methods.isOwnerOrAdminCross(helper.ethCrossAccount.fromAddress(adminDeprecated)).call()).to.be.true;87 });88 });8990 itEth('cross account admin can mint', async ({helper}) => {91 92 const owner = await helper.eth.createAccountWithBalance(donor);93 const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', 'a', 'b', 'uri');94 const adminEth = (await helper.eth.createAccountWithBalance(donor)).toLowerCase();95 const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth);96 const [adminSub] = await helper.arrange.createAccounts([100n], donor);97 const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub);98 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner, true);99100 101 await expect(collectionEvm.methods.mint(owner).send({from: adminEth})).to.be.rejected;102 await expect(helper.nft.mintToken(adminSub, {collectionId, owner: {Ethereum: owner}})).to.be.rejectedWith(/common.PublicMintingNotAllowed/);103104 105 await collectionEvm.methods.addCollectionAdminCross(adminCrossEth).send();106 await collectionEvm.methods.addCollectionAdminCross(adminCrossSub).send();107 await collectionEvm.methods.mint(owner).send({from: adminEth});108 await helper.nft.mintToken(adminSub, {collectionId, owner: {Ethereum: owner}});109110 expect(await helper.collection.getLastTokenId(collectionId)).to.eq(2);111 });112113 itEth('cannot add invalid cross account admin', async ({helper}) => {114 const owner = await helper.eth.createAccountWithBalance(donor);115 const [admin] = await helper.arrange.createAccounts([100n, 100n], donor);116117 const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');118 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);119120 const adminCross = {121 eth: helper.address.substrateToEth(admin.address),122 sub: admin.addressRaw,123 };124 await expect(collectionEvm.methods.addCollectionAdminCross(adminCross).send()).to.be.rejected;125 });126127 itEth('can verify owner with methods.isOwnerOrAdmin[Cross]', async ({helper, privateKey}) => {128 const owner = await helper.eth.createAccountWithBalance(donor);129 const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');130131 const adminDeprecated = helper.eth.createAccount();132 const admin1Cross = helper.ethCrossAccount.fromKeyringPair(await privateKey('admin'));133 const admin2Cross = helper.ethCrossAccount.fromAddress(helper.address.substrateToEth((await privateKey('admin3')).address));134 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner, true);135136 137 expect(await collectionEvm.methods.isOwnerOrAdmin(adminDeprecated).call()).to.be.false;138 expect(await collectionEvm.methods.isOwnerOrAdminCross(admin1Cross).call()).to.be.false;139 expect(await collectionEvm.methods.isOwnerOrAdminCross(admin2Cross).call()).to.be.false;140141 await collectionEvm.methods.addCollectionAdmin(adminDeprecated).send();142 await collectionEvm.methods.addCollectionAdminCross(admin1Cross).send();143 await collectionEvm.methods.addCollectionAdminCross(admin2Cross).send();144145 146 expect(await collectionEvm.methods.isOwnerOrAdmin(adminDeprecated).call()).to.be.true;147 148 expect(await collectionEvm.methods.isOwnerOrAdminCross(admin1Cross).call()).to.be.true;149 expect(await collectionEvm.methods.isOwnerOrAdminCross(admin2Cross).call()).to.be.true;150 });151152 153 itEth('(!negative tests!) Add admin by ADMIN is not allowed', async ({helper}) => {154 const owner = await helper.eth.createAccountWithBalance(donor);155 const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');156157 const admin = await helper.eth.createAccountWithBalance(donor);158 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner, true);159 await collectionEvm.methods.addCollectionAdmin(admin).send();160161 const user = helper.eth.createAccount();162 await expect(collectionEvm.methods.addCollectionAdmin(user).call({from: admin}))163 .to.be.rejectedWith('NoPermission');164165 const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);166 expect(adminList.length).to.be.eq(1);167 expect(adminList[0].asEthereum.toString().toLocaleLowerCase())168 .to.be.eq(admin.toLocaleLowerCase());169 });170171 172 itEth('(!negative tests!) Add admin by USER is not allowed', async ({helper}) => {173 const owner = await helper.eth.createAccountWithBalance(donor);174 const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');175176 const notAdmin = await helper.eth.createAccountWithBalance(donor);177 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner, true);178179 const user = helper.eth.createAccount();180 await expect(collectionEvm.methods.addCollectionAdmin(user).call({from: notAdmin}))181 .to.be.rejectedWith('NoPermission');182183 const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);184 expect(adminList.length).to.be.eq(0);185 });186187 itEth('(!negative tests!) Add [cross] admin by ADMIN is not allowed', async ({helper}) => {188 const owner = await helper.eth.createAccountWithBalance(donor);189 const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');190191 const [admin, notAdmin] = await helper.arrange.createAccounts([10n, 10n], donor);192 const adminCross = helper.ethCrossAccount.fromKeyringPair(admin);193 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);194 await collectionEvm.methods.addCollectionAdminCross(adminCross).send();195196 const notAdminCross = helper.ethCrossAccount.fromKeyringPair(notAdmin);197 await expect(collectionEvm.methods.addCollectionAdminCross(notAdminCross).call({from: adminCross.eth}))198 .to.be.rejectedWith('NoPermission');199200 const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);201 expect(adminList.length).to.be.eq(1);202203 const admin0Cross = helper.ethCrossAccount.fromKeyringPair(adminList[0]);204 expect(admin0Cross.eth.toLocaleLowerCase())205 .to.be.eq(adminCross.eth.toLocaleLowerCase());206 });207208 itEth('(!negative tests!) Add [cross] admin by USER is not allowed', async ({helper}) => {209 const owner = await helper.eth.createAccountWithBalance(donor);210 const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');211212 const notAdmin0 = await helper.eth.createAccountWithBalance(donor);213 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);214 const [notAdmin1] = await helper.arrange.createAccounts([10n], donor);215 const notAdmin1Cross = helper.ethCrossAccount.fromKeyringPair(notAdmin1);216 await expect(collectionEvm.methods.addCollectionAdminCross(notAdmin1Cross).call({from: notAdmin0}))217 .to.be.rejectedWith('NoPermission');218219 const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);220 expect(adminList.length).to.be.eq(0);221 });222});223224describe('Remove collection admins', () => {225 let donor: IKeyringPair;226227 before(async function() {228 await usingEthPlaygrounds(async (_helper, privateKey) => {229 donor = await privateKey({url: import.meta.url});230 });231 });232233 234 itEth('Remove admin by owner', async ({helper}) => {235 const owner = await helper.eth.createAccountWithBalance(donor);236 const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');237238 const newAdmin = helper.eth.createAccount();239 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner, true);240 await collectionEvm.methods.addCollectionAdmin(newAdmin).send();241242 {243 const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);244 expect(adminList.length).to.be.eq(1);245 expect(adminList[0].asEthereum.toString().toLocaleLowerCase())246 .to.be.eq(newAdmin.toLocaleLowerCase());247 }248249 await collectionEvm.methods.removeCollectionAdmin(newAdmin).send();250 const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);251 expect(adminList.length).to.be.eq(0);252 });253254 itEth('Remove [cross] admin by owner', async ({helper}) => {255 const owner = await helper.eth.createAccountWithBalance(donor);256 const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');257258 const [adminSub] = await helper.arrange.createAccounts([10n], donor);259 const adminEth = (await helper.eth.createAccountWithBalance(donor)).toLowerCase();260 const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub);261 const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth);262263 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);264 await collectionEvm.methods.addCollectionAdminCross(adminCrossSub).send();265 await collectionEvm.methods.addCollectionAdminCross(adminCrossEth).send();266267 {268 const adminList = await helper.collection.getAdmins(collectionId);269 expect(adminList).to.deep.include({Substrate: adminSub.address});270 expect(adminList).to.deep.include({Ethereum: adminEth});271 }272273 await collectionEvm.methods.removeCollectionAdminCross(adminCrossSub).send();274 await collectionEvm.methods.removeCollectionAdminCross(adminCrossEth).send();275 const adminList = await helper.collection.getAdmins(collectionId);276 expect(adminList.length).to.be.eq(0);277278 279 await expect(helper.nft.mintToken(adminSub, {collectionId, owner: {Substrate: adminSub.address}})).to.be.rejectedWith(/common.PublicMintingNotAllowed/);280 await expect(collectionEvm.methods.mint(adminEth).send({from: adminEth})).to.be.rejected;281 });282283 284 itEth('(!negative tests!) Remove admin by ADMIN is not allowed', async ({helper}) => {285 const owner = await helper.eth.createAccountWithBalance(donor);286 const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');287288 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner, true);289290 const admin0 = await helper.eth.createAccountWithBalance(donor);291 await collectionEvm.methods.addCollectionAdmin(admin0).send();292 const admin1 = await helper.eth.createAccountWithBalance(donor);293 await collectionEvm.methods.addCollectionAdmin(admin1).send();294295 await expect(collectionEvm.methods.removeCollectionAdmin(admin1).call({from: admin0}))296 .to.be.rejectedWith('NoPermission');297 {298 const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);299 expect(adminList.length).to.be.eq(2);300 expect(adminList.toString().toLocaleLowerCase())301 .to.be.deep.contains(admin0.toLocaleLowerCase())302 .to.be.deep.contains(admin1.toLocaleLowerCase());303 }304 });305306 307 itEth('(!negative tests!) Remove admin by USER is not allowed', async ({helper}) => {308 const owner = await helper.eth.createAccountWithBalance(donor);309 const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');310311 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner, true);312313 const admin = await helper.eth.createAccountWithBalance(donor);314 await collectionEvm.methods.addCollectionAdmin(admin).send();315 const notAdmin = helper.eth.createAccount();316317 await expect(collectionEvm.methods.removeCollectionAdmin(admin).call({from: notAdmin}))318 .to.be.rejectedWith('NoPermission');319 {320 const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);321 expect(adminList[0].asEthereum.toString().toLocaleLowerCase())322 .to.be.eq(admin.toLocaleLowerCase());323 expect(adminList.length).to.be.eq(1);324 }325 });326327 itEth('(!negative tests!) Remove [cross] admin by ADMIN is not allowed', async ({helper}) => {328 const owner = await helper.eth.createAccountWithBalance(donor);329 const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');330331 const [admin1] = await helper.arrange.createAccounts([10n], donor);332 const admin1Cross = helper.ethCrossAccount.fromKeyringPair(admin1);333 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);334 await collectionEvm.methods.addCollectionAdminCross(admin1Cross).send();335336 const [admin2] = await helper.arrange.createAccounts([10n], donor);337 const admin2Cross = helper.ethCrossAccount.fromKeyringPair(admin2);338 await collectionEvm.methods.addCollectionAdminCross(admin2Cross).send();339340 await expect(collectionEvm.methods.removeCollectionAdminCross(admin1Cross).call({from: admin2Cross.eth}))341 .to.be.rejectedWith('NoPermission');342343 const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);344 expect(adminList.length).to.be.eq(2);345 expect(adminList.toString().toLocaleLowerCase())346 .to.be.deep.contains(admin1.address.toLocaleLowerCase())347 .to.be.deep.contains(admin2.address.toLocaleLowerCase());348 });349350 itEth('(!negative tests!) Remove [cross] admin by USER is not allowed', async ({helper}) => {351 const owner = await helper.eth.createAccountWithBalance(donor);352 const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');353354 const [adminSub] = await helper.arrange.createAccounts([10n], donor);355 const adminSubCross = helper.ethCrossAccount.fromKeyringPair(adminSub);356 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);357 await collectionEvm.methods.addCollectionAdminCross(adminSubCross).send();358 const notAdminEth = await helper.eth.createAccountWithBalance(donor);359360 await expect(collectionEvm.methods.removeCollectionAdminCross(adminSubCross).call({from: notAdminEth}))361 .to.be.rejectedWith('NoPermission');362363 const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);364 expect(adminList.length).to.be.eq(1);365 expect(adminList[0].asSubstrate.toString().toLocaleLowerCase())366 .to.be.eq(adminSub.address.toLocaleLowerCase());367 });368});369370371describe('Change owner tests', () => {372 let donor: IKeyringPair;373374 before(async function() {375 await usingEthPlaygrounds(async (_helper, privateKey) => {376 donor = await privateKey({url: import.meta.url});377 });378 });379380 itEth('Change owner', async ({helper}) => {381 const owner = await helper.eth.createAccountWithBalance(donor);382 const newOwner = await helper.eth.createAccountWithBalance(donor);383 const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');384 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner, true);385386 await collectionEvm.methods.changeCollectionOwner(newOwner).send();387388 expect(await collectionEvm.methods.isOwnerOrAdmin(owner).call()).to.be.false;389 expect(await collectionEvm.methods.isOwnerOrAdmin(newOwner).call()).to.be.true;390 });391392 itEth('change owner call fee', async ({helper}) => {393 const owner = await helper.eth.createAccountWithBalance(donor);394 const newOwner = await helper.eth.createAccountWithBalance(donor);395 const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');396 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner, true);397 const cost = await recordEthFee(helper, owner, () => collectionEvm.methods.changeCollectionOwner(newOwner).send());398 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));399 expect(cost > 0);400 });401402 itEth('(!negative tests!) call setOwner by non owner', async ({helper}) => {403 const owner = await helper.eth.createAccountWithBalance(donor);404 const newOwner = await helper.eth.createAccountWithBalance(donor);405 const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');406 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner, true);407408 await expect(collectionEvm.methods.changeCollectionOwner(newOwner).send({from: newOwner})).to.be.rejected;409 expect(await collectionEvm.methods.isOwnerOrAdmin(newOwner).call()).to.be.false;410 });411});412413describe('Change substrate owner tests', () => {414 let donor: IKeyringPair;415416 before(async function() {417 await usingEthPlaygrounds(async (_helper, privateKey) => {418 donor = await privateKey({url: import.meta.url});419 });420 });421422 itEth('Change owner [cross]', async ({helper}) => {423 const owner = await helper.eth.createAccountWithBalance(donor);424 const ownerEth = await helper.eth.createAccountWithBalance(donor);425 const ownerCrossEth = helper.ethCrossAccount.fromAddress(ownerEth);426 const [ownerSub] = await helper.arrange.createAccounts([10n], donor);427 const ownerCrossSub = helper.ethCrossAccount.fromKeyringPair(ownerSub);428429 const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');430 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);431432 expect(await collectionEvm.methods.isOwnerOrAdminCross(ownerCrossSub).call()).to.be.false;433434 435 await collectionEvm.methods.changeCollectionOwnerCross(ownerCrossEth).send({from: owner});436 expect(await collectionEvm.methods.isOwnerOrAdminCross(ownerCrossEth).call()).to.be.true;437 expect(await helper.collection.getData(collectionId))438 .to.have.property('normalizedOwner').that.is.eq(helper.address.ethToSubstrate(ownerEth));439440 441 await collectionEvm.methods.changeCollectionOwnerCross(ownerCrossSub).send({from: ownerEth});442 expect(await collectionEvm.methods.isOwnerOrAdminCross(ownerCrossSub).call()).to.be.true;443 expect(await helper.collection.getData(collectionId))444 .to.have.property('normalizedOwner').that.is.eq(helper.address.normalizeSubstrate(ownerSub.address));445 });446447 itEth.skip('change owner call fee', async ({helper}) => {448 const owner = await helper.eth.createAccountWithBalance(donor);449 const [newOwner] = await helper.arrange.createAccounts([10n], donor);450 const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');451 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);452453 const cost = await recordEthFee(helper, owner, () => collectionEvm.methods.setOwnerSubstrate(newOwner.addressRaw).send());454 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));455 expect(cost > 0);456 });457458 itEth('(!negative tests!) call setOwner by non owner [cross]', async ({helper}) => {459 const owner = await helper.eth.createAccountWithBalance(donor);460 const otherReceiver = await helper.eth.createAccountWithBalance(donor);461 const [newOwner] = await helper.arrange.createAccounts([10n], donor);462 const newOwnerCross = helper.ethCrossAccount.fromKeyringPair(newOwner);463 const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');464 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);465466 await expect(collectionEvm.methods.changeCollectionOwnerCross(newOwnerCross).send({from: otherReceiver})).to.be.rejected;467 expect(await collectionEvm.methods.isOwnerOrAdminCross(newOwnerCross).call()).to.be.false;468 });469});