difftreelog
fix calibrate.ts
in: master
1 file changed
tests/src/calibrate.tsdiffbeforeafterboth1import {IKeyringPair} from '@polkadot/types/types';23import {usingEthPlaygrounds, EthUniqueHelper} from './eth/util';456function linearRegression(points: { x: bigint, y: bigint }[]) {7 let sumxy = 0n;8 let sumx = 0n;9 let sumy = 0n;10 let sumx2 = 0n;11 const n = points.length;12 for (let i = 0; i < n; i++) {13 const p = points[i];14 sumxy += p.x * p.y;15 sumx += p.x;16 sumy += p.y;17 sumx2 += p.x * p.x;18 }1920 const nb = BigInt(n);2122 const a = (nb * sumxy - sumx * sumy) / (nb * sumx2 - sumx * sumx);23 const b = (sumy - a * sumx) / nb;2425 return {a, b};26}2728// JS has no builtin function to calculate sqrt of bigint29// https://stackoverflow.com/a/53684036/619016930function sqrt(value: bigint) {31 if (value < 0n) {32 throw 'square root of negative numbers is not supported';33 }3435 if (value < 2n) {36 return value;37 }3839 function newtonIteration(n: bigint, x0: bigint): bigint {40 const x1 = ((n / x0) + x0) >> 1n;41 if (x0 === x1 || x0 === (x1 - 1n)) {42 return x0;43 }44 return newtonIteration(n, x1);45 }4647 return newtonIteration(value, 1n);48}4950function _error(points: { x: bigint, y: bigint }[], hypothesis: (a: bigint) => bigint) {51 return sqrt(points.map(p => {52 const v = hypothesis(p.x);53 const vv = p.y;5455 return (v - vv) ** 2n;56 }).reduce((a, b) => a + b, 0n) / BigInt(points.length));57}5859async function calibrateWeightToFee(helper: EthUniqueHelper, privateKey: (account: string) => Promise<IKeyringPair>) {60 const alice = await privateKey('//Alice');61 const bob = await privateKey('//Bob');62 const dataPoints = [];6364 {65 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'});66 const token = await collection.mintToken(alice, {Substrate: alice.address});67 const aliceBalanceBefore = await helper.balance.getSubstrate(alice.address);68 await token.transfer(alice, {Substrate: bob.address});69 const aliceBalanceAfter = await helper.balance.getSubstrate(alice.address);7071 console.log(`Original price: ${Number(aliceBalanceBefore-aliceBalanceAfter)/Number(helper.balance.getOneTokenNominal())} UNQ`);72 }7374 const api = helper.getApi();75 const defaultCoeff = (api.consts.configuration.defaultWeightToFeeCoefficient as any).toBigInt();76 for (let i = -5; i < 5; i++) {77 await helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.configuration.setWeightToFeeCoefficientOverride(defaultCoeff + defaultCoeff / 1000n * BigInt(i))));7879 const coefficient = (await api.query.configuration.weightToFeeCoefficientOverride() as any).toBigInt();80 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'});81 const token = await collection.mintToken(alice, {Substrate: alice.address});8283 const aliceBalanceBefore = await helper.balance.getSubstrate(alice.address);84 await token.transfer(alice, {Substrate: bob.address});85 const aliceBalanceAfter = await helper.balance.getSubstrate(alice.address);8687 const transferPrice = aliceBalanceBefore - aliceBalanceAfter;8889 dataPoints.push({x: transferPrice, y: coefficient});90 }91 const {a, b} = linearRegression(dataPoints);9293 // console.log(`Error: ${error(dataPoints, x => a*x+b)}`);9495 const perfectValue = a * helper.balance.getOneTokenNominal() / 10n + b;96 await helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.configuration.setWeightToFeeCoefficientOverride(perfectValue.toString())));9798 {99 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'});100 const token = await collection.mintToken(alice, {Substrate: alice.address});101 const aliceBalanceBefore = await helper.balance.getSubstrate(alice.address);102 await token.transfer(alice, {Substrate: bob.address});103 const aliceBalanceAfter = await helper.balance.getSubstrate(alice.address);104105 console.log(`Calibrated price: ${Number(aliceBalanceBefore-aliceBalanceAfter)/Number(helper.balance.getOneTokenNominal())} UNQ`);106 }107}108109async function calibrateMinGasPrice(helper: EthUniqueHelper, privateKey: (account: string) => Promise<IKeyringPair>) {110 const alice = await privateKey('//Alice');111 const caller = await helper.eth.createAccountWithBalance(alice);112 const receiver = helper.eth.createAccount();113 const dataPoints = [];114115 {116 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'});117 const token = await collection.mintToken(alice, {Ethereum: caller});118119 const address = helper.ethAddress.fromCollectionId(collection.collectionId);120 const contract = helper.ethNativeContract.collection(address, 'nft', caller);121122 const cost = await helper.eth.calculateFee({Ethereum: caller}, () => contract.methods.transfer(receiver, token.tokenId).send({from: caller, gas: helper.eth.DEFAULT_GAS}));123124 console.log(`Original price: ${Number(cost)/Number(helper.balance.getOneTokenNominal())} UNQ`);125 }126127 const api = helper.getApi();128 const defaultCoeff = (api.consts.configuration.defaultMinGasPrice as any).toBigInt();129 for (let i = -8; i < 8; i++) {130 const gasPrice = defaultCoeff + defaultCoeff / 100000n * BigInt(i);131 const gasPriceStr = '0x' + gasPrice.toString(16);132 await helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.configuration.setMinGasPriceOverride(gasPrice)));133134 const coefficient = (await api.query.configuration.minGasPriceOverride() as any).toBigInt();135 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'});136 const token = await collection.mintToken(alice, {Ethereum: caller});137138 const address = helper.ethAddress.fromCollectionId(collection.collectionId);139 const contract = helper.ethNativeContract.collection(address, 'nft', caller);140141 const transferPrice = await helper.eth.calculateFee({Ethereum: caller}, () => contract.methods.transfer(receiver, token.tokenId).send({from: caller, gasPrice: gasPriceStr, gas: helper.eth.DEFAULT_GAS}));142143 dataPoints.push({x: transferPrice, y: coefficient});144 }145146 const {a, b} = linearRegression(dataPoints);147148 // console.log(`Error: ${error(dataPoints, x => a*x+b)}`);149150 // * 0.15 = * 10000 / 66666151 const perfectValue = a * helper.balance.getOneTokenNominal() * 1000000n / 6666666n + b;152 await helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.configuration.setMinGasPriceOverride(perfectValue.toString())));153154 {155 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'});156 const token = await collection.mintToken(alice, {Ethereum: caller});157158 const address = helper.ethAddress.fromCollectionId(collection.collectionId);159 const contract = helper.ethNativeContract.collection(address, 'nft', caller);160161 const cost = await helper.eth.calculateFee({Ethereum: caller}, () => contract.methods.transfer(receiver, token.tokenId).send({from: caller, gas: helper.eth.DEFAULT_GAS}));162163 console.log(`Calibrated price: ${Number(cost)/Number(helper.balance.getOneTokenNominal())} UNQ`);164 }165}166167(async () => {168 await usingEthPlaygrounds(async (helper: EthUniqueHelper, privateKey) => {169 // Second run slightly reduces error sometimes, as price line is not actually straight, this is a curve170171 await calibrateWeightToFee(helper, privateKey);172 await calibrateWeightToFee(helper, privateKey);173174 await calibrateMinGasPrice(helper, privateKey);175 await calibrateMinGasPrice(helper, privateKey);176 });177})();1import {IKeyringPair} from '@polkadot/types/types';2import {usingEthPlaygrounds, EthUniqueHelper} from './eth/util';34function linearRegression(points: { x: bigint, y: bigint }[]) {5 let sumxy = 0n;6 let sumx = 0n;7 let sumy = 0n;8 let sumx2 = 0n;9 const n = points.length;10 for (let i = 0; i < n; i++) {11 const p = points[i];12 sumxy += p.x * p.y;13 sumx += p.x;14 sumy += p.y;15 sumx2 += p.x * p.x;16 }1718 const nb = BigInt(n);1920 const precision = 100000000n;2122 // This is a workaround to beat the lack of precision of the `Number` type.23 // We divide `BigInt`s. But since it is an integer division, we should take care of the precision on our own.24 // After the division we can convert the result back to the `Number` and then we set the correct precision.25 // It is crucial to have the correct slope for the regression line.26 const a = Number((precision * (nb * sumxy - sumx * sumy)) / (nb * sumx2 - sumx * sumx)) / Number(precision);27 const b = (Number(sumy) - a * Number(sumx)) / Number(nb);2829 return {a, b};30}3132// JS has no builtin function to calculate sqrt of bigint33// https://stackoverflow.com/a/53684036/619016934function sqrt(value: bigint) {35 if (value < 0n) {36 throw 'square root of negative numbers is not supported';37 }3839 if (value < 2n) {40 return value;41 }4243 function newtonIteration(n: bigint, x0: bigint): bigint {44 const x1 = ((n / x0) + x0) >> 1n;45 if (x0 === x1 || x0 === (x1 - 1n)) {46 return x0;47 }48 return newtonIteration(n, x1);49 }5051 return newtonIteration(value, 1n);52}5354function _error(points: { x: bigint, y: bigint }[], hypothesis: (a: bigint) => bigint) {55 return sqrt(points.map(p => {56 const v = hypothesis(p.x);57 const vv = p.y;5859 return (v - vv) ** 2n;60 }).reduce((a, b) => a + b, 0n) / BigInt(points.length));61}6263async function calibrateWeightToFee(helper: EthUniqueHelper, privateKey: (account: string) => Promise<IKeyringPair>) {64 const alice = await privateKey('//Alice');65 const bob = await privateKey('//Bob');66 const dataPoints = [];6768 {69 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'});70 const token = await collection.mintToken(alice, {Substrate: alice.address});71 const aliceBalanceBefore = await helper.balance.getSubstrate(alice.address);72 await token.transfer(alice, {Substrate: bob.address});73 const aliceBalanceAfter = await helper.balance.getSubstrate(alice.address);7475 console.log(`\t[NFT Transfer] Original price: ${Number(aliceBalanceBefore-aliceBalanceAfter)/Number(helper.balance.getOneTokenNominal())} UNQ`);76 }7778 const api = helper.getApi();79 const base = (await api.query.configuration.weightToFeeCoefficientOverride() as any).toBigInt();80 for (let i = -5; i < 5; i++) {81 await helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.configuration.setWeightToFeeCoefficientOverride(base + base / 1000n * BigInt(i))));8283 const coefficient = (await api.query.configuration.weightToFeeCoefficientOverride() as any).toBigInt();84 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'});85 const token = await collection.mintToken(alice, {Substrate: alice.address});8687 const aliceBalanceBefore = await helper.balance.getSubstrate(alice.address);88 await token.transfer(alice, {Substrate: bob.address});89 const aliceBalanceAfter = await helper.balance.getSubstrate(alice.address);9091 const transferPrice = aliceBalanceBefore - aliceBalanceAfter;9293 dataPoints.push({x: transferPrice, y: coefficient});94 }95 const {a, b} = linearRegression(dataPoints);9697 // console.log(`Error: ${error(dataPoints, x => a*x+b)}`);9899 const perfectValue = BigInt(Math.ceil(a * Number(helper.balance.getOneTokenNominal()) / 10 + b));100 await helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.configuration.setWeightToFeeCoefficientOverride(perfectValue.toString())));101102 {103 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'});104 const token = await collection.mintToken(alice, {Substrate: alice.address});105 const aliceBalanceBefore = await helper.balance.getSubstrate(alice.address);106 await token.transfer(alice, {Substrate: bob.address});107 const aliceBalanceAfter = await helper.balance.getSubstrate(alice.address);108109 console.log(`\t[NFT Transfer] Calibrated price: ${Number(aliceBalanceBefore-aliceBalanceAfter)/Number(helper.balance.getOneTokenNominal())} UNQ`);110 }111}112113async function calibrateMinGasPrice(helper: EthUniqueHelper, privateKey: (account: string) => Promise<IKeyringPair>) {114 const alice = await privateKey('//Alice');115 const caller = await helper.eth.createAccountWithBalance(alice);116 const receiver = helper.eth.createAccount();117 const dataPoints = [];118119 {120 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'});121 const token = await collection.mintToken(alice, {Ethereum: caller});122123 const address = helper.ethAddress.fromCollectionId(collection.collectionId);124 const contract = helper.ethNativeContract.collection(address, 'nft', caller);125126 const cost = await helper.eth.calculateFee({Ethereum: caller}, () => contract.methods.transfer(receiver, token.tokenId).send({from: caller, gas: helper.eth.DEFAULT_GAS}));127128 console.log(`\t[ETH NFT transfer] Original price: ${Number(cost)/Number(helper.balance.getOneTokenNominal())} UNQ`);129 }130131 const api = helper.getApi();132 // const defaultCoeff = (api.consts.configuration.defaultMinGasPrice as any).toBigInt();133 const base = (await api.query.configuration.minGasPriceOverride() as any).toBigInt();134 for (let i = -8; i < 8; i++) {135 const gasPrice = base + base / 100000n * BigInt(i);136 const gasPriceStr = '0x' + gasPrice.toString(16);137 await helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.configuration.setMinGasPriceOverride(gasPrice)));138139 const coefficient = (await api.query.configuration.minGasPriceOverride() as any).toBigInt();140 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'});141 const token = await collection.mintToken(alice, {Ethereum: caller});142143 const address = helper.ethAddress.fromCollectionId(collection.collectionId);144 const contract = helper.ethNativeContract.collection(address, 'nft', caller);145146 const transferPrice = await helper.eth.calculateFee({Ethereum: caller}, () => contract.methods.transfer(receiver, token.tokenId).send({from: caller, gasPrice: gasPriceStr, gas: helper.eth.DEFAULT_GAS}));147148 dataPoints.push({x: transferPrice, y: coefficient});149 }150151 const {a, b} = linearRegression(dataPoints);152153 // console.log(`Error: ${error(dataPoints, x => a*x+b)}`);154155 // * 0.15 = * 10000 / 66666156 const perfectValue = BigInt(Math.ceil(a * Number(helper.balance.getOneTokenNominal() * 1000000n / 6666666n) + b));157 await helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.configuration.setMinGasPriceOverride(perfectValue.toString())));158159 {160 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'});161 const token = await collection.mintToken(alice, {Ethereum: caller});162163 const address = helper.ethAddress.fromCollectionId(collection.collectionId);164 const contract = helper.ethNativeContract.collection(address, 'nft', caller);165166 const cost = await helper.eth.calculateFee({Ethereum: caller}, () => contract.methods.transfer(receiver, token.tokenId).send({from: caller, gas: helper.eth.DEFAULT_GAS}));167168 console.log(`\t[ETH NFT transfer] Calibrated price: ${Number(cost)/Number(helper.balance.getOneTokenNominal())} UNQ`);169 }170}171172(async () => {173 await usingEthPlaygrounds(async (helper: EthUniqueHelper, privateKey) => {174 // Subsequent runs reduce error, as price line is not actually straight, this is a curve175176 const iterations = 3;177178 console.log('[Calibrate WeightToFee]');179 for (let i = 0; i < iterations; i++) {180 await calibrateWeightToFee(helper, privateKey);181 }182183 console.log();184185 console.log('[Calibrate MinGasPrice]');186 for (let i = 0; i < iterations; i++) {187 await calibrateMinGasPrice(helper, privateKey);188 }189 });190})();