difftreelog
test price calibration script
in: master
3 files changed
tests/.eslintrc.jsondiffbeforeafterboth--- a/tests/.eslintrc.json
+++ b/tests/.eslintrc.json
@@ -55,7 +55,13 @@
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-explicit-any": "off",
- "@typescript-eslint/no-unused-vars": "warn",
+ "@typescript-eslint/no-unused-vars": [
+ "warn",
+ {
+ "varsIgnorePattern": "_.+",
+ "argsIgnorePattern": "_.+"
+ }
+ ],
"no-async-promise-executor": "warn",
"@typescript-eslint/no-empty-interface": "off",
"prefer-const": [
tests/src/calibrate.tsdiffbeforeafterboth1import {ApiPromise} from '@polkadot/api';2import {IKeyringPair} from '@polkadot/types/types';3import Web3 from 'web3';4import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, GAS_ARGS, recordEthFee, usingWeb3} from './eth/util/helpers';5import usingApi, {executeTransaction} from './substrate/substrate-api';6import {createCollectionExpectSuccess, createItemExpectSuccess, transferExpectSuccess, UNIQUE, waitNewBlocks} from './util/helpers';7import nonFungibleAbi from './eth/nonFungibleAbi.json';89function linearRegression(points: { x: bigint, y: bigint }[]) {10 let sumxy = 0n;11 let sumx = 0n;12 let sumy = 0n;13 let sumx2 = 0n;14 const n = points.length;15 for (let i = 0; i < n; i++) {16 const p = points[i];17 sumxy += p.x * p.y;18 sumx += p.x;19 sumy += p.y;20 sumx2 += p.x * p.x;21 }2223 const nb = BigInt(n);2425 const a = (nb * sumxy - sumx * sumy) / (nb * sumx2 - sumx * sumx);26 const b = (sumy - a * sumx) / nb;2728 return {a, b};29}3031// JS has no builtin function to calculate sqrt of bigint32// https://stackoverflow.com/a/53684036/619016933function sqrt(value: bigint) {34 if (value < 0n) {35 throw 'square root of negative numbers is not supported';36 }3738 if (value < 2n) {39 return value;40 }4142 function newtonIteration(n: bigint, x0: bigint): bigint {43 const x1 = ((n / x0) + x0) >> 1n;44 if (x0 === x1 || x0 === (x1 - 1n)) {45 return x0;46 }47 return newtonIteration(n, x1);48 }4950 return newtonIteration(value, 1n);51}5253function _error(points: { x: bigint, y: bigint }[], hypothesis: (a: bigint) => bigint) {54 return sqrt(points.map(p => {55 const v = hypothesis(p.x);56 const vv = p.y;5758 return (v - vv) ** 2n;59 }).reduce((a, b) => a + b, 0n) / BigInt(points.length));60}6162async function calibrateWeightToFee(api: ApiPromise, privateKey: (account: string) => IKeyringPair) {63 const alice = privateKey('//Alice');64 const bob = privateKey('//Bob');65 const dataPoints = [];6667 {68 const collectionId = await createCollectionExpectSuccess();69 const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT');70 const aliceBalanceBefore = (await api.query.system.account(alice.address)).data.free.toBigInt();71 await transferExpectSuccess(collectionId, tokenId, alice, bob, 1, 'NFT');72 const aliceBalanceAfter = (await api.query.system.account(alice.address)).data.free.toBigInt();7374 console.log(`Original price: ${Number(aliceBalanceBefore-aliceBalanceAfter)/Number(UNIQUE)} UNQ`);75 }7677 const defaultCoeff = (api.consts.configuration.defaultWeightToFeeCoefficient as any).toBigInt();78 for (let i = -5; i < 5; i++) {79 await executeTransaction(api, alice, api.tx.sudo.sudo(api.tx.configuration.setWeightToFeeCoefficientOverride(defaultCoeff + defaultCoeff / 1000n * BigInt(i))));8081 const coefficient = (await api.query.configuration.weightToFeeCoefficientOverride() as any).toBigInt();82 const collectionId = await createCollectionExpectSuccess();83 const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT');8485 const aliceBalanceBefore = (await api.query.system.account(alice.address)).data.free.toBigInt();86 await transferExpectSuccess(collectionId, tokenId, alice, bob, 1, 'NFT');87 const aliceBalanceAfter = (await api.query.system.account(alice.address)).data.free.toBigInt();8889 const transferPrice = aliceBalanceBefore - aliceBalanceAfter;9091 dataPoints.push({x: transferPrice, y: coefficient});92 }93 const {a, b} = linearRegression(dataPoints);9495 // console.log(`Error: ${error(dataPoints, x => a*x+b)}`);9697 const perfectValue = a * UNIQUE / 10n + b;98 await executeTransaction(api, alice, api.tx.sudo.sudo(api.tx.configuration.setWeightToFeeCoefficientOverride(perfectValue.toString())));99100 {101 const collectionId = await createCollectionExpectSuccess();102 const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT');103 const aliceBalanceBefore = (await api.query.system.account(alice.address)).data.free.toBigInt();104 await transferExpectSuccess(collectionId, tokenId, alice, bob, 1, 'NFT');105 const aliceBalanceAfter = (await api.query.system.account(alice.address)).data.free.toBigInt();106107 console.log(`Calibrated price: ${Number(aliceBalanceBefore-aliceBalanceAfter)/Number(UNIQUE)} UNQ`);108 }109}110111async function calibrateMinGasPrice(api: ApiPromise, web3: Web3, privateKey: (account: string) => IKeyringPair) {112 const alice = privateKey('//Alice');113 const caller = await createEthAccountWithBalance(api, web3, privateKey);114 const receiver = createEthAccount(web3);115 const dataPoints = [];116117 {118 const collectionId = await createCollectionExpectSuccess();119 const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', {Ethereum: caller});120121 const address = collectionIdToAddress(collectionId);122 const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});123124 const cost = await recordEthFee(api, caller, () => contract.methods.transfer(receiver, tokenId).send(caller));125126 console.log(`Original price: ${Number(cost)/Number(UNIQUE)} UNQ`);127 }128129 const defaultCoeff = (api.consts.configuration.defaultMinGasPrice as any).toBigInt();130 for (let i = -8; i < 8; i++) {131 const gasPrice = defaultCoeff + defaultCoeff / 100000n * BigInt(i);132 const gasPriceStr = '0x' + gasPrice.toString(16);133 await executeTransaction(api, alice, api.tx.sudo.sudo(api.tx.configuration.setMinGasPriceOverride(gasPrice)));134135 const coefficient = (await api.query.configuration.minGasPriceOverride() as any).toBigInt();136 const collectionId = await createCollectionExpectSuccess();137 const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', {Ethereum: caller});138139 const address = collectionIdToAddress(collectionId);140 const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, gasPrice: gasPriceStr, ...GAS_ARGS});141142 const transferPrice = await recordEthFee(api, caller, () => contract.methods.transfer(receiver, tokenId).send(caller));143144 dataPoints.push({x: transferPrice, y: coefficient});145 }146147 const {a, b} = linearRegression(dataPoints);148149 // console.log(`Error: ${error(dataPoints, x => a*x+b)}`);150151 // * 0.15 = * 10000 / 66666152 const perfectValue = a * UNIQUE * 1000000n / 6666666n + b;153 await executeTransaction(api, alice, api.tx.sudo.sudo(api.tx.configuration.setMinGasPriceOverride(perfectValue.toString())));154155 {156 const collectionId = await createCollectionExpectSuccess();157 const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', {Ethereum: caller});158159 const address = collectionIdToAddress(collectionId);160 const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});161162 const cost = await recordEthFee(api, caller, () => contract.methods.transfer(receiver, tokenId).send(caller));163164 console.log(`Calibrated price: ${Number(cost)/Number(UNIQUE)} UNQ`);165 }166}167168(async () => {169 await usingApi(async (api, privateKey) => {170 // Second run slightly reduces error sometimes, as price line is not actually straight, this is a curve171172 await calibrateWeightToFee(api, privateKey);173 await calibrateWeightToFee(api, privateKey);174175 await usingWeb3(async web3 => {176 await calibrateMinGasPrice(api, web3, privateKey);177 await calibrateMinGasPrice(api, web3, privateKey);178 });179180 await api.disconnect();181 });182})();tests/src/calibrateApply.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/calibrateApply.ts
@@ -0,0 +1,38 @@
+import {readFile, writeFile} from 'fs/promises';
+import path from 'path';
+import usingApi from './substrate/substrate-api';
+
+const formatNumber = (num: string): string => num.split('').reverse().join('').replace(/([0-9]{3})/g, '$1_').split('').reverse().join('').replace(/^_/, '');
+
+(async () => {
+ let weightToFeeCoefficientOverride: string;
+ let minGasPriceOverride: string;
+ await usingApi(async (api, _privateKey) => {
+ weightToFeeCoefficientOverride = (await api.query.configuration.weightToFeeCoefficientOverride() as any).toBigInt().toString();
+ minGasPriceOverride = (await api.query.configuration.minGasPriceOverride() as any).toBigInt().toString();
+ });
+ const constantsFile = path.resolve(__dirname, '../../primitives/common/src/constants.rs');
+ let constants = (await readFile(constantsFile)).toString();
+
+ let weight2feeFound = false;
+ constants = constants.replace(/(\/\*<weight2fee>\*\/)[0-9_]+(\/\*<\/weight2fee>\*\/)/, (_f, p, s) => {
+ weight2feeFound = true;
+ return p+formatNumber(weightToFeeCoefficientOverride)+s;
+ });
+ if (!weight2feeFound) {
+ throw new Error('failed to find weight2fee marker in source code');
+ }
+
+ let minGasPriceFound = false;
+ constants = constants.replace(/(\/\*<mingasprice>\*\/)[0-9_]+(\/\*<\/mingasprice>\*\/)/, (_f, p, s) => {
+ minGasPriceFound = true;
+ return p+formatNumber(minGasPriceOverride)+s;
+ });
+ if (!minGasPriceFound) {
+ throw new Error('failed to find mingasprice marker in source code');
+ }
+
+ await writeFile(constantsFile, constants);
+})().catch(e => {
+ console.log(e.stack);
+});