git.delta.rocks / unique-network / refs/commits / d2c9363e553d

difftreelog

tests: performance test for NFT tokens

Andy Smith2023-09-27parent: #1acfc39.patch.diff
in: master

2 files changed

modifiedtests/package.jsondiffbeforeafterboth
before · tests/package.json
1{2  "name": "unique-tests",3  "version": "1.0.0",4  "description": "Unique Chain Tests",5  "main": "",6  "devDependencies": {7    "@polkadot/typegen": "10.9.1",8    "@types/chai": "^4.3.3",9    "@types/chai-as-promised": "^7.1.5",10    "@types/chai-like": "^1.1.1",11    "@types/chai-subset": "^1.3.3",12    "@types/mocha": "^10.0.0",13    "@types/node": "^20.4.2",14    "@typescript-eslint/eslint-plugin": "^6.0.0",15    "@typescript-eslint/parser": "^6.0.0",16    "chai": "^4.3.6",17    "chai-subset": "^1.6.0",18    "eslint": "^8.45.0",19    "eslint-plugin-mocha": "^10.1.0",20    "mocha": "^10.1.0",21    "mochawesome": "^7.1.3",22    "ts-node": "^10.9.1",23    "typescript": "^5.1.6"24  },25  "mocha": {26    "timeout": 9999999,27    "require": [28      "ts-node/register"29    ]30  },31  "scripts": {32    "lint": "eslint --ext .ts,.js src/",33    "fix": "yarn lint --fix",34    "setup": "ts-node --esm ./src/util/globalSetup.ts",35    "setIdentities": "ts-node --esm ./src/util/identitySetter.ts",36    "checkRelayIdentities": "ts-node --esm ./src/util/relayIdentitiesChecker.ts",37    "frankenstein": "ts-node --esm ./src/util/frankenstein.ts",38    "_test": "yarn setup && mocha --timeout 9999999 --loader=ts-node/esm.mjs",39    "_testParallel": "yarn setup && mocha --timeout 9999999 --parallel --loader=ts-node/esm.mjs",40    "test": "yarn _test './src/**/*.*test.ts'",41    "testParallelFull": "yarn testParallel && yarn testSequential",42    "testParallel": "yarn _testParallel './src/**/*.test.ts'",43    "testSequential": "yarn _test './src/**/*.seqtest.ts'",44    "testStructure": "yarn _test ./**/nesting/*.*test.ts",45    "testGovernance": "RUN_GOV_TESTS=1 yarn _test ./**/governance/*.*test.ts",46    "testEth": "yarn _test './**/eth/**/*.*test.ts'",47    "testEthNesting": "yarn _test './**/eth/nesting/**/*.*test.ts'",48    "testEthFractionalizer": "yarn _test './**/eth/fractionalizer/**/*.*test.ts'",49    "testEthMarketplace": "yarn _test './**/eth/marketplace/**/*.*test.ts'",50    "testEthMarket": "yarn _test './**/eth/marketplace-v2/**/*.*test.ts'",51    "testSub": "yarn _test './**/sub/**/*.*test.ts'",52    "testSubNesting": "yarn _test './**/sub/nesting/**/*.*test.ts'",53    "testEvent": "yarn _test ./src/check-event/*.*test.ts",54    "testEthPayable": "yarn _test './**/eth/payable.test.ts'",55    "testEvmCoder": "yarn _test './**/eth/evmCoder.test.ts'",56    "testNesting": "yarn _test ./**/nest.test.ts",57    "testUnnesting": "yarn _test ./**/unnest.test.ts",58    "testProperties": "yarn _test ./**/collectionProperties.*test.ts ./**/tokenProperties.*test.ts ./**/getPropertiesRpc.test.ts",59    "testCollectionProperties": "yarn _test ./**/collectionProperties.*test.ts",60    "testTokenProperties": "yarn _test ./**/tokenProperties.*test.ts",61    "testMigration": "yarn _test ./**/nesting/migration-check.test.ts",62    "testAddCollectionAdmin": "yarn _test ./**/addCollectionAdmin.test.ts",63    "testSetCollectionLimits": "yarn _test ./**/setCollectionLimits.test.ts",64    "testChangeCollectionOwner": "yarn _test ./**/change-collection-owner.test.ts",65    "testSetCollectionSponsor": "yarn _test ./**/setCollectionSponsor.test.ts",66    "testConfirmSponsorship": "yarn _test ./**/confirmSponsorship.test.ts",67    "testRemoveCollectionAdmin": "yarn _test ./**/removeCollectionAdmin.test.ts",68    "testRemoveCollectionSponsor": "yarn _test ./**/removeCollectionSponsor.test.ts",69    "testAllowLists": "yarn _test ./**/allowLists.test.ts",70    "testConnection": "yarn _test ./**/connection.test.ts",71    "testContracts": "yarn _test ./**/contracts.test.ts",72    "testCreateItem": "yarn _test ./**/createItem.test.ts",73    "testCreateMultipleItems": "yarn _test ./**/createMultipleItems.test.ts",74    "testCreateMultipleItemsEx": "yarn _test ./**/createMultipleItemsEx.test.ts",75    "testApprove": "yarn _test ./**/approve.test.ts",76    "testTransferFrom": "yarn _test ./**/transferFrom.test.ts",77    "testCreateCollection": "yarn _test ./**/createCollection.test.ts",78    "testDestroyCollection": "yarn _test ./**/destroyCollection.test.ts",79    "testToggleContractAllowList": "yarn _test ./**/toggleContractAllowList.test.ts",80    "testAddToContractAllowList": "yarn _test ./**/addToContractAllowList.test.ts",81    "testTransfer": "yarn _test ./**/transfer.test.ts",82    "testBurnItem": "yarn _test ./**/burnItem.test.ts",83    "testAdminTransferAndBurn": "yarn _test ./**/adminTransferAndBurn.test.ts",84    "testSetPermissions": "yarn _test ./**/setPermissions.test.ts",85    "testCreditFeesToTreasury": "yarn _test ./**/creditFeesToTreasury.seqtest.ts",86    "testContractSponsoring": "yarn _test ./**/eth/contractSponsoring.test.ts",87    "testEnableContractSponsoring": "yarn _test ./**/enableContractSponsoring.test.ts",88    "testRemoveFromContractAllowList": "yarn _test ./**/removeFromContractAllowList.test.ts",89    "testSetContractSponsoringRateLimit": "yarn _test ./**/setContractSponsoringRateLimit.test.ts",90    "testSetOffchainSchema": "yarn _test ./**/setOffchainSchema.test.ts",91    "testNextSponsoring": "yarn _test ./**/nextSponsoring.test.ts",92    "testOverflow": "yarn _test ./**/overflow.test.ts",93    "testMaintenance": "yarn _test ./**/maintenance.seqtest.ts",94    "testInflation": "yarn _test ./**/inflation.seqtest.ts",95    "testScheduler": "yarn _test ./**/scheduler.seqtest.ts",96    "testSchedulingEVM": "yarn _test ./**/eth/scheduling.test.ts",97    "testPalletPresence": "yarn _test ./**/pallet-presence.test.ts",98    "testEnableDisableTransfers": "yarn _test ./**/enableDisableTransfer.test.ts",99    "testLimits": "yarn _test ./**/limits.test.ts",100    "testEthCreateNFTCollection": "yarn _test ./**/eth/createNFTCollection.test.ts",101    "testEthCreateRFTCollection": "yarn _test ./**/eth/createRFTCollection.test.ts",102    "testEthNFT": "yarn _test ./**/eth/nonFungible.test.ts",103    "testRFT": "yarn _test ./**/refungible.test.ts",104    "testEthRFT": "yarn _test ./**/eth/reFungible.test.ts ./**/eth/reFungibleToken.test.ts",105    "testFT": "yarn _test ./**/fungible.test.ts",106    "testEthFT": "yarn _test ./**/eth/fungible.test.ts",107    "testRPC": "yarn _test ./**/rpc.test.ts",108    "testPromotion": "yarn _test ./**/appPromotion/*test.ts",109    "testApiConsts": "yarn _test ./**/apiConsts.test.ts",110    "testCouncil": "yarn _test ./**/council.*test.ts",111    "testDemocracy": "yarn _test ./**/democracy.*test.ts",112    "testCollators": "RUN_COLLATOR_TESTS=1 yarn _test ./**/collator-selection/**.*test.ts --timeout 49999999",113    "testCollatorSelection": "RUN_COLLATOR_TESTS=1 yarn _test ./**/collatorSelection.*test.ts --timeout 49999999",114    "testIdentity": "RUN_COLLATOR_TESTS=1 yarn _test ./**/identity.*test.ts --timeout 49999999",115    "testXcmUnique": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmUnique.test.ts",116    "testXcmQuartz": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmQuartz.test.ts",117    "testXcmOpal": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmOpal.test.ts",118    "testXcmTransferAcala": "yarn _test ./**/xcm/xcmTransferAcala.test.ts acalaId=2000 uniqueId=5000",119    "testXcmTransferStatemine": "yarn _test ./**/xcm/xcmTransferStatemine.test.ts statemineId=1000 uniqueId=5000",120    "testXcmTransferMoonbeam": "yarn _test ./**/xcm/xcmTransferMoonbeam.test.ts",121    "benchMintingFee": "ts-node src/benchmarks/mintFee/benchmark.ts",122    "load": "yarn _test './**/*.load.ts'",123    "loadTransfer": "ts-node src/transfer.nload.ts",124    "polkadot-types-fetch-metadata": "yarn ts-node --esm src/fetchMetadata.ts",125    "polkadot-types-from-defs": "ts-node --esm ./node_modules/.bin/polkadot-types-from-defs --endpoint src/interfaces/metadata.json --input src/interfaces/ --package .",126    "polkadot-types-from-chain": "ts-node --esm ./node_modules/.bin/polkadot-types-from-chain --endpoint src/interfaces/metadata.json --output src/interfaces/ --package .",127    "polkadot-types": "echo \"export default {}\" > src/interfaces/lookup.ts && yarn polkadot-types-fetch-metadata && yarn polkadot-types-from-defs && yarn polkadot-types-from-defs && yarn polkadot-types-from-chain",128    "generateEnv": "ts-node --esm ./src/generateEnv.ts"129  },130  "author": "",131  "license": "SEE LICENSE IN ../LICENSE",132  "homepage": "",133  "dependencies": {134    "@openzeppelin/contracts": "^4.9.2",135    "@polkadot/api": "10.9.1",136    "@polkadot/rpc-core": "^10.9.1",137    "@polkadot/util": "12.3.2",138    "@polkadot/util-crypto": "12.3.2",139    "@polkadot/wasm-crypto-asmjs": "^7.2.1",140    "@polkadot/wasm-crypto-wasm": "^7.2.1",141    "@rmrk-team/evm-contracts": "^1.2.1",142    "@typechain/web3-v1": "^6.0.3",143    "chai-as-promised": "^7.1.1",144    "chai-like": "^1.1.1",145    "csv-writer": "^1.6.0",146    "find-process": "^1.4.7",147    "lossless-json": "^2.0.9",148    "solc": "0.8.20",149    "typechain": "^8.2.0",150    "web3": "1.10.0"151  },152  "resolutions": {153    "decode-uri-component": "^0.2.1"154  },155  "type": "module",156  "packageManager": "yarn@3.6.1"157}
addedtests/src/performance.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/performance.test.ts
@@ -0,0 +1,175 @@
+// Copyright 2019-2023 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 <http://www.gnu.org/licenses/>.
+
+import {ApiPromise} from '@polkadot/api';
+import {IKeyringPair} from '@polkadot/types/types';
+import {expect, itSub, usingPlaygrounds} from './util';
+import {ICrossAccountId, IProperty} from './util/playgrounds/types';
+import {UniqueHelper} from './util/playgrounds/unique';
+
+describe('Performace tests', () => {
+  let alice: IKeyringPair;
+  const MAX_TOKENS_TO_MINT = 200;
+
+  before(async () => {
+    await usingPlaygrounds(async (helper, privateKey) => {
+      const donor = await privateKey({url: import.meta.url});
+      [alice] = await helper.arrange.createAccounts([100_000n], donor);
+    });
+  });
+
+  itSub('NFT tokens minting', async ({helper}) => {
+    const propertyKey = 'prop-a';
+    const collection = await helper.nft.mintCollection(alice, {
+      name: 'test properties',
+      description: 'test properties collection',
+      tokenPrefix: 'TPC',
+      tokenPropertyPermissions: [
+        {key: propertyKey, permission: {mutable: true, collectionAdmin: true, tokenOwner: true}},
+      ],
+    });
+
+
+    const results = [];
+    const step = 1_000;
+    const sizeOfKey = sizeOfEncodedStr(propertyKey);
+    let currentSize = step;
+    let startCount = 0;
+    let minterFunc = tryMintUnsafeRPC;
+    try {
+      startCount = await tryMintUnsafeRPC(helper, alice, MAX_TOKENS_TO_MINT, collection.collectionId, {Substrate: alice.address});
+    }
+    catch (e) {
+      startCount = await tryMintExplicit(helper, alice, MAX_TOKENS_TO_MINT, collection.collectionId, {Substrate: alice.address});
+      minterFunc = tryMintExplicit;
+    }
+    results.push({propertySize: 0, tokens: startCount});
+
+    while(currentSize <= 32_000) {
+      const property = {key: propertyKey, value: 'A'.repeat(currentSize - sizeOfKey - sizeOfInt(currentSize))};
+      const maxTokens = Math.ceil(results.map(x => x.tokens).reduce((a, b) => a + b) / results.length);
+      const tokens = await minterFunc(helper, alice, maxTokens, collection.collectionId, {Substrate: alice.address}, property);
+      results.push({propertySize: sizeOfProperty(property), tokens});
+      currentSize += step;
+      await helper.wait.newBlocks(2);
+    }
+
+    expect(results).to.be.deep.equal([
+      {propertySize: 0, tokens: 200},
+      {propertySize: 1000, tokens: 149},
+      {propertySize: 2000, tokens: 149},
+      {propertySize: 3000, tokens: 149},
+      {propertySize: 4000, tokens: 149},
+      {propertySize: 5000, tokens: 149},
+      {propertySize: 6000, tokens: 149},
+      {propertySize: 7000, tokens: 149},
+      {propertySize: 8000, tokens: 149},
+      {propertySize: 9000, tokens: 149},
+      {propertySize: 10000, tokens: 149},
+      {propertySize: 11000, tokens: 149},
+      {propertySize: 12000, tokens: 149},
+      {propertySize: 13000, tokens: 149},
+      {propertySize: 14000, tokens: 149},
+      {propertySize: 15000, tokens: 149},
+      {propertySize: 16000, tokens: 149},
+      {propertySize: 17000, tokens: 149},
+      {propertySize: 18000, tokens: 149},
+      {propertySize: 19000, tokens: 149},
+      {propertySize: 20000, tokens: 149},
+      {propertySize: 21000, tokens: 149},
+      {propertySize: 22000, tokens: 149},
+      {propertySize: 23000, tokens: 149},
+      {propertySize: 24000, tokens: 149},
+      {propertySize: 25000, tokens: 149},
+      {propertySize: 26000, tokens: 149},
+      {propertySize: 27000, tokens: 145},
+      {propertySize: 28000, tokens: 140},
+      {propertySize: 29000, tokens: 135},
+      {propertySize: 30000, tokens: 130},
+      {propertySize: 31000, tokens: 126},
+      {propertySize: 32000, tokens: 122},
+    ]);
+  });
+});
+
+
+const dryRun = async (api: ApiPromise, signer: IKeyringPair, tx: any) => {
+  const signed = await tx.signAsync(signer);
+  const dryRun = await api.rpc.system.dryRun(signed.toHex());
+  return dryRun.isOk && dryRun.asOk.isOk;
+};
+
+const getTokens = (tokensCount: number, owner: ICrossAccountId, property?: IProperty) => (new Array(tokensCount)).fill(0).map(() => {
+  const token = {owner} as {owner: ICrossAccountId, properties?: IProperty[]};
+  if(property) token.properties = [property];
+  return token;
+});
+
+const tryMintUnsafeRPC = async (helper: UniqueHelper, signer: IKeyringPair, tokensCount: number, collectionId: number, owner: ICrossAccountId, property?: IProperty): Promise<number> => {
+  if(tokensCount < 10) console.log('try mint', tokensCount, 'tokens');
+  const tokens = getTokens(tokensCount, owner, property);
+  const tx = helper.constructApiCall('api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}]);
+  if(!(await dryRun(helper.getApi(), signer, tx))) {
+    if(tokensCount < 2) return 0;
+    return await tryMintUnsafeRPC(helper, signer, tokensCount - 1, collectionId, owner, property);
+  }
+  await helper.executeExtrinsic(signer, 'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}]);
+  return tokensCount;
+};
+
+const tryMintExplicit = async (helper: UniqueHelper, signer: IKeyringPair, tokensCount: number, collectionId: number, owner: ICrossAccountId, property?: IProperty): Promise<number> => {
+  const tokens = getTokens(tokensCount, owner, property);
+  try {
+    await helper.executeExtrinsic(signer, 'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}]);
+  }
+  catch (e) {
+    if(tokensCount < 2) return 0;
+    return await tryMintExplicit(helper, signer, tokensCount - 1, collectionId, owner, property);
+  }
+  return tokensCount;
+};
+
+
+function sizeOfByteProperty(prop: IProperty) {
+  return sizeOfEncodedBytes(prop.key) + sizeOfEncodedBytes(prop.value!);
+}
+
+function sizeOfProperty(prop: IProperty) {
+  return sizeOfEncodedStr(prop.key) + sizeOfEncodedStr(prop.value!);
+}
+
+function sizeOfInt(i: number) {
+  if(i < 0 || i > 0xffffffff) throw new Error('out of range');
+  if(i < 0b11_1111) {
+    return 1;
+  } else if(i < 0b11_1111_1111_1111) {
+    return 2;
+  } else if(i < 0b11_1111_1111_1111_1111_1111_1111_1111) {
+    return 4;
+  } else {
+    return 5;
+  }
+}
+
+const UTF8_ENCODER = new TextEncoder();
+function sizeOfEncodedStr(v: string) {
+  const encoded = UTF8_ENCODER.encode(v);
+  return sizeOfInt(encoded.length) + encoded.length;
+}
+
+function sizeOfEncodedBytes(bytes: Uint8Array | string) {
+  return sizeOfInt(bytes.length) + bytes.length;
+}