From 9a57db1ccb4e1280738a3512d1a6dbb931f4c63a Mon Sep 17 00:00:00 2001 From: Farhad Hakimov Date: Wed, 12 Oct 2022 09:13:52 +0000 Subject: [PATCH] Merge pull request #645 from UniqueNetwork/tests/feed-alices-promotion Split promotion tests to sequential and parallel --- --- a/tests/package.json +++ b/tests/package.json @@ -21,21 +21,22 @@ }, "mocha": { "timeout": 9999999, - "require": "ts-node/register" + "require": ["ts-node/register", "./src/util/playgrounds/globalSetup.ts"] }, "scripts": { "lint": "eslint --ext .ts,.js src/", "fix": "eslint --ext .ts,.js src/ --fix", - "feedAlices": "ts-node ./src/util/playgrounds/feedAlices.ts", - "test": "yarn feedAlices && mocha --parallel --timeout 9999999 -r ts-node/register './src/**/*.test.ts'", - "testSequential": "yarn feedAlices && mocha --timeout 9999999 -r ts-node/register './src/**/*.seqtest.ts'", - "testStructure": "yarn feedAlices && mocha --parallel --timeout 9999999 -r ts-node/register ./**/nesting/**.test.ts", - "testEth": "yarn feedAlices && mocha --parallel --timeout 9999999 -r ts-node/register './**/eth/**/*.test.ts'", - "testEthNesting": "yarn feedAlices && mocha --parallel --timeout 9999999 -r ts-node/register './**/eth/nesting/**/*.test.ts'", - "testEthFractionalizer": "yarn feedAlices && mocha --parallel --timeout 9999999 -r ts-node/register './**/eth/fractionalizer/**/*.test.ts'", - "testEthMarketplace": "yarn feedAlices && mocha --parallel --timeout 9999999 -r ts-node/register './**/eth/marketplace/**/*.test.ts'", - "testEvent": "yarn feedAlices && mocha --parallel --timeout 9999999 -r ts-node/register ./src/check-event/*.test.ts", + "test": "yarn testParallel && yarn testSequential", + "testParallel": "mocha --parallel --timeout 9999999 -r ts-node/register './src/**/*.test.ts'", + "testSequential": "mocha --timeout 9999999 -r ts-node/register './src/**/*.seqtest.ts'", + "testDevnode": "mocha --timeout 9999999 -r ts-node/register './src/**/*test.ts'", + "testStructure": "mocha --parallel --timeout 9999999 -r ts-node/register ./**/nesting/**.test.ts", + "testEth": "mocha --parallel --timeout 9999999 -r ts-node/register './**/eth/**/*.test.ts'", + "testEthNesting": "mocha --parallel --timeout 9999999 -r ts-node/register './**/eth/nesting/**/*.test.ts'", + "testEthFractionalizer": "mocha --parallel --timeout 9999999 -r ts-node/register './**/eth/fractionalizer/**/*.test.ts'", + "testEthMarketplace": "mocha --parallel --timeout 9999999 -r ts-node/register './**/eth/marketplace/**/*.test.ts'", + "testEvent": "mocha --parallel --timeout 9999999 -r ts-node/register ./src/check-event/*.test.ts", "testRmrk": "mocha --timeout 9999999 -r ts-node/register ./**/rmrk/**test.ts", "testEthPayable": "mocha --timeout 9999999 -r ts-node/register './**/eth/payable.test.ts'", --- /dev/null +++ b/tests/src/app-promotion.seqtest.ts @@ -0,0 +1,74 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +import {IKeyringPair} from '@polkadot/types/types'; +import {itSub, usingPlaygrounds, Pallets, requirePalletsOrSkip} from './util/playgrounds'; +import {expect} from './eth/util/playgrounds'; + +let superuser: IKeyringPair; +let donor: IKeyringPair; +let palletAdmin: IKeyringPair; + +describe('App promotion', () => { + before(async function () { + await usingPlaygrounds(async (helper, privateKey) => { + requirePalletsOrSkip(this, helper, [Pallets.AppPromotion]); + superuser = await privateKey('//Alice'); + donor = await privateKey({filename: __filename}); + palletAdmin = await privateKey('//PromotionAdmin'); + const api = helper.getApi(); + await helper.signTransaction(superuser, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address}))); + }); + }); + + describe('admin adress', () => { + itSub('can be set by sudo only', async ({helper}) => { + const api = helper.getApi(); + const [nonAdmin] = await helper.arrange.createAccounts([10n], donor); + // nonAdmin can not set admin not from himself nor as a sudo + await expect(helper.signTransaction(nonAdmin, api.tx.appPromotion.setAdminAddress({Substrate: nonAdmin.address}))).to.be.rejected; + await expect(helper.signTransaction(nonAdmin, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: nonAdmin.address})))).to.be.rejected; + }); + + itSub('can be any valid CrossAccountId', async ({helper}) => { + // We are not going to set an eth address as a sponsor, + // but we do want to check, it doesn't break anything; + const api = helper.getApi(); + const [account] = await helper.arrange.createAccounts([10n], donor); + const ethAccount = helper.address.substrateToEth(account.address); + // Alice sets Ethereum address as a sudo. Then Substrate address back... + await expect(helper.signTransaction(superuser, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Ethereum: ethAccount})))).to.be.fulfilled; + await expect(helper.signTransaction(superuser, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})))).to.be.fulfilled; + + // ...It doesn't break anything; + const collection = await helper.nft.mintCollection(account, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'}); + await expect(helper.signTransaction(account, api.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected; + }); + + itSub('can be reassigned', async ({helper}) => { + const api = helper.getApi(); + const [oldAdmin, newAdmin, collectionOwner] = await helper.arrange.createAccounts([10n, 10n, 10n], donor); + const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'}); + + await expect(helper.signTransaction(superuser, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: oldAdmin.address})))).to.be.fulfilled; + await expect(helper.signTransaction(superuser, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: newAdmin.address})))).to.be.fulfilled; + await expect(helper.signTransaction(oldAdmin, api.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected; + + await expect(helper.signTransaction(newAdmin, api.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.fulfilled; + }); + }); +}); + --- a/tests/src/app-promotion.test.ts +++ b/tests/src/app-promotion.test.ts @@ -16,17 +16,14 @@ import {IKeyringPair} from '@polkadot/types/types'; import {itSub, usingPlaygrounds, Pallets, requirePalletsOrSkip} from './util/playgrounds'; -import {encodeAddress} from '@polkadot/util-crypto'; -import {stringToU8a} from '@polkadot/util'; import {DevUniqueHelper} from './util/playgrounds/unique.dev'; import {itEth, expect, SponsoringMode} from './eth/util/playgrounds'; -let superuser: IKeyringPair; let donor: IKeyringPair; let palletAdmin: IKeyringPair; let nominal: bigint; -const palletAddress = calculatePalleteAddress('appstake'); -let accounts: IKeyringPair[] = []; +let palletAddress: string; +let accounts: IKeyringPair[]; const LOCKING_PERIOD = 20n; // 20 blocks of relay const UNLOCKING_PERIOD = 10n; // 10 blocks of parachain const rewardAvailableInBlock = (stakedInBlock: bigint) => { @@ -38,14 +35,10 @@ before(async function () { await usingPlaygrounds(async (helper, privateKey) => { requirePalletsOrSkip(this, helper, [Pallets.AppPromotion]); - superuser = await privateKey('//Alice'); donor = await privateKey({filename: __filename}); - [palletAdmin] = await helper.arrange.createAccounts([100n], donor); - const api = helper.getApi(); - await helper.signTransaction(superuser, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address}))); + palletAddress = helper.arrange.calculatePalleteAddress('appstake'); + palletAdmin = await privateKey('//PromotionAdmin'); nominal = helper.balance.getOneTokenNominal(); - await helper.balance.transferToSubstrate(donor, palletAdmin.address, 1000n * nominal); - await helper.balance.transferToSubstrate(donor, palletAddress, 1000n * nominal); accounts = await helper.arrange.createCrowd(100, 1000n, donor); // create accounts-pool to speed up tests }); }); @@ -223,58 +216,10 @@ expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(100n * nominal); expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(0n); })); - }); - }); - - describe('admin adress', () => { - itSub('can be set by sudo only', async ({helper}) => { - const api = helper.getApi(); - const nonAdmin = accounts.pop()!; - // nonAdmin can not set admin not from himself nor as a sudo - await expect(helper.signTransaction(nonAdmin, api.tx.appPromotion.setAdminAddress({Substrate: nonAdmin.address}))).to.be.rejected; - await expect(helper.signTransaction(nonAdmin, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: nonAdmin.address})))).to.be.rejected; - - // Alice can - await expect(helper.signTransaction(superuser, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})))).to.be.fulfilled; - }); - - itSub('can be any valid CrossAccountId', async ({helper}) => { - // We are not going to set an eth address as a sponsor, - // but we do want to check, it doesn't break anything; - const api = helper.getApi(); - const account = accounts.pop()!; - const ethAccount = helper.address.substrateToEth(account.address); - // Alice sets Ethereum address as a sudo. Then Substrate address back... - await expect(helper.signTransaction(superuser, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Ethereum: ethAccount})))).to.be.fulfilled; - await expect(helper.signTransaction(superuser, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})))).to.be.fulfilled; - - // ...It doesn't break anything; - const collection = await helper.nft.mintCollection(account, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'}); - await expect(helper.signTransaction(account, api.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected; - }); - - itSub('can be reassigned', async ({helper}) => { - const api = helper.getApi(); - const [oldAdmin, newAdmin, collectionOwner] = [accounts.pop()!, accounts.pop()!, accounts.pop()!]; - const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'}); - - await expect(helper.signTransaction(superuser, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: oldAdmin.address})))).to.be.fulfilled; - await expect(helper.signTransaction(superuser, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: newAdmin.address})))).to.be.fulfilled; - await expect(helper.signTransaction(oldAdmin, api.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected; - - await expect(helper.signTransaction(newAdmin, api.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.fulfilled; }); }); describe('collection sponsoring', () => { - before(async function () { - await usingPlaygrounds(async (helper) => { - const api = helper.getApi(); - const tx = api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})); - await helper.signTransaction(superuser, tx); - }); - }); - itSub('should actually sponsor transactions', async ({helper}) => { const api = helper.getApi(); const [collectionOwner, tokenSender, receiver] = [accounts.pop()!, accounts.pop()!, accounts.pop()!]; @@ -708,11 +653,6 @@ }); }); }); - -function calculatePalleteAddress(palletId: any) { - const address = stringToU8a(('modl' + palletId).padEnd(32, '\0')); - return encodeAddress(address); -} function calculateIncome(base: bigint, calcPeriod: bigint, iter = 0): bigint { const DAY = 7200n; --- a/tests/src/util/playgrounds/feedAlices.ts +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -import * as path from 'path'; -import {promises as fs} from 'fs'; -import {usingPlaygrounds} from '.'; - -async function getFiles(rootPath: string): Promise { - const files = await fs.readdir(rootPath, {withFileTypes: true}); - const filenames: string[] = []; - for (const entry of files) { - const res = path.resolve(rootPath, entry.name); - if (entry.isDirectory()) { - filenames.push(...await getFiles(res)); - } else { - filenames.push(res); - } - } - return filenames; -} - -const fundFilenames = async () => { - await usingPlaygrounds(async (helper, privateKey) => { - const oneToken = helper.balance.getOneTokenNominal(); - const alice = await privateKey('//Alice'); - const nonce = await helper.chain.getNonce(alice.address); - const filenames = await getFiles(path.resolve(__dirname, '../..')); - - // batching is actually undesired, it takes away the time while all the transactions actually succeed - const batchSize = 300; - let balanceGrantedCounter = 0; - for (let b = 0; b < filenames.length; b += batchSize) { - const tx = []; - let batchBalanceGrantedCounter = 0; - for (let i = 0; batchBalanceGrantedCounter < batchSize && b + i < filenames.length; i++) { - const f = filenames[b + i]; - if (!f.endsWith('.test.ts') || f.includes('.outdated')) continue; - const account = await privateKey({filename: f, ignoreFundsPresence: true}); - const aliceBalance = await helper.balance.getSubstrate(account.address); - - if (aliceBalance < 100_000n * oneToken) { - tx.push(helper.executeExtrinsic( - alice, - 'api.tx.balances.transfer', - [account.address, 1_000_000n * oneToken], - true, - {nonce: nonce + balanceGrantedCounter++}, - ).then(() => true).catch(() => {console.error(`Transaction to ${path.basename(f)} registered as failed. Strange.`); return false;})); - batchBalanceGrantedCounter++; - } - } - - if(tx.length > 0) { - console.log(`Granting funds to ${batchBalanceGrantedCounter} filename accounts.`); - const result = await Promise.all(tx); - if (result && result.lastIndexOf(false) > -1) throw new Error('The transactions actually probably succeeded, should check the balances.'); - } - } - - if (balanceGrantedCounter == 0) console.log('No account needs additional funding.'); - }); -}; - -const fundFilenamesWithRetries = async (retriesLeft: number): Promise => { - if (retriesLeft <= 0) return Promise.resolve(false); - return fundFilenames() - .then(() => Promise.resolve(true)) - .catch(e => { - console.error(e); - console.error(`Some transactions might have failed. ${retriesLeft > 1 ? 'Retrying...' : 'Something is wrong.'}\n`); - return fundFilenamesWithRetries(--retriesLeft); - }); -}; - -fundFilenamesWithRetries(3).then((result) => process.exit(result ? 0 : 1)).catch(e => { - console.error(e); - process.exit(1); -}); --- /dev/null +++ b/tests/src/util/playgrounds/globalSetup.ts @@ -0,0 +1,102 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +import {usingPlaygrounds, Pallets} from './index'; +import * as path from 'path'; +import {promises as fs} from 'fs'; + +// This file is used in the mocha package.json section +export async function mochaGlobalSetup() { + await usingPlaygrounds(async (helper, privateKey) => { + try { + // 1. Create donors + await fundFilenamesWithRetries(3) + .then((result) => { + if (!result) process.exit(1); + }); + + // 2. Set up App Promotion admin + const missingPallets = helper.fetchMissingPalletNames([Pallets.AppPromotion]); + if (missingPallets.length === 0) { + const superuser = await privateKey('//Alice'); + const palletAddress = helper.arrange.calculatePalleteAddress('appstake'); + const palletAdmin = await privateKey('//PromotionAdmin'); + const api = helper.getApi(); + await helper.signTransaction(superuser, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address}))); + const nominal = helper.balance.getOneTokenNominal(); + await helper.balance.transferToSubstrate(superuser, palletAdmin.address, 1000n * nominal); + await helper.balance.transferToSubstrate(superuser, palletAddress, 1000n * nominal); + } + } catch (error) { + console.error(error); + process.exit(1); + } + }); +} + +async function getFiles(rootPath: string): Promise { + const files = await fs.readdir(rootPath, {withFileTypes: true}); + const filenames: string[] = []; + for (const entry of files) { + const res = path.resolve(rootPath, entry.name); + if (entry.isDirectory()) { + filenames.push(...await getFiles(res)); + } else { + filenames.push(res); + } + } + return filenames; +} + +const fundFilenames = async () => { + await usingPlaygrounds(async (helper, privateKey) => { + const oneToken = helper.balance.getOneTokenNominal(); + const alice = await privateKey('//Alice'); + const nonce = await helper.chain.getNonce(alice.address); + const filenames = await getFiles(path.resolve(__dirname, '../..')); + + // batching is actually undesired, it takes away the time while all the transactions actually succeed + const batchSize = 300; + let balanceGrantedCounter = 0; + for (let b = 0; b < filenames.length; b += batchSize) { + const tx = []; + let batchBalanceGrantedCounter = 0; + for (let i = 0; batchBalanceGrantedCounter < batchSize && b + i < filenames.length; i++) { + const f = filenames[b + i]; + if (!f.endsWith('.test.ts') && !f.endsWith('seqtest.ts') || f.includes('.outdated')) continue; + const account = await privateKey({filename: f, ignoreFundsPresence: true}); + const aliceBalance = await helper.balance.getSubstrate(account.address); + + if (aliceBalance < 100_000n * oneToken) { + tx.push(helper.executeExtrinsic( + alice, + 'api.tx.balances.transfer', + [account.address, 1_000_000n * oneToken], + true, + {nonce: nonce + balanceGrantedCounter++}, + ).then(() => true).catch(() => {console.error(`Transaction to ${path.basename(f)} registered as failed. Strange.`); return false;})); + batchBalanceGrantedCounter++; + } + } + + if(tx.length > 0) { + console.log(`Granting funds to ${batchBalanceGrantedCounter} filename accounts.`); + const result = await Promise.all(tx); + if (result && result.lastIndexOf(false) > -1) throw new Error('The transactions actually probably succeeded, should check the balances.'); + } + } + + if (balanceGrantedCounter == 0) console.log('No account needs additional funding.'); + }); +}; + +const fundFilenamesWithRetries = async (retriesLeft: number): Promise => { + if (retriesLeft <= 0) return Promise.resolve(false); + return fundFilenames() + .then(() => Promise.resolve(true)) + .catch(e => { + console.error(e); + console.error(`Some transactions might have failed. ${retriesLeft > 1 ? 'Retrying...' : 'Something is wrong.'}\n`); + return fundFilenamesWithRetries(--retriesLeft); + }); +}; --- a/tests/src/util/playgrounds/unique.dev.ts +++ b/tests/src/util/playgrounds/unique.dev.ts @@ -1,7 +1,8 @@ // Copyright 2019-2022 Unique Network (Gibraltar) Ltd. // SPDX-License-Identifier: Apache-2.0 -import {mnemonicGenerate} from '@polkadot/util-crypto'; +import {stringToU8a} from '@polkadot/util'; +import {encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto'; import {UniqueHelper} from './unique'; import {ApiPromise, WsProvider} from '@polkadot/api'; import * as defs from '../../interfaces/definitions'; @@ -243,6 +244,11 @@ return balance; } + + calculatePalleteAddress(palletId: any) { + const address = stringToU8a(('modl' + palletId).padEnd(32, '\0')); + return encodeAddress(address); + } } class WaitGroup { -- gitstuff