difftreelog
refactor use fractionals in calibrate
in: master
1 file changed
tests/src/calibrate.tsdiffbeforeafterboth1import {IKeyringPair} from '@polkadot/types/types';1import {IKeyringPair} from '@polkadot/types/types';2import {usingEthPlaygrounds, EthUniqueHelper} from './eth/util';2import {usingEthPlaygrounds, EthUniqueHelper} from './eth/util';34class Fract {5 static ZERO = new Fract(0n);6 constructor(public readonly a: bigint, public readonly b: bigint = 1n) {7 if (b === 0n) throw new Error('division by zero');8 if (b < 0n) throw new Error('missing normalization');9 }1011 mul(other: Fract) {12 return new Fract(this.a * other.a, this.b * other.b);13 }1415 div(other: Fract) {16 return this.mul(other.inv());17 }1819 plus(other: Fract) {20 return new Fract(this.a * other.b + other.a * this.b, this.b * other.b);21 }2223 minus(other: Fract) {24 return this.plus(other.neg());25 }2627 neg() {28 return new Fract(-this.a, this.b);29 }30 inv() {31 return new Fract(this.b, this.a);32 }3334 toBigInt() {35 return this.a / this.b;36 }3738 lt(other: Fract) {39 return this.a * other.b < other.a * this.b;40 }41 eq(other: Fract) {42 return this.a * other.b === other.a * this.b;43 }4445 sqrt() {46 if (this.a < 0n) {47 throw 'square root of negative numbers is not supported';48 }4950 if (this.lt(new Fract(2n))) {51 return this;52 }5354 function newtonIteration(n: Fract, x0: Fract): Fract {55 const x1 = rpn(n, x0, '/', x0, '+', new Fract(2n), '/');56 if (x0.eq(x1) || x0.eq(x1.minus(new Fract(1n)))) {57 return x0;58 }59 return newtonIteration(n, x1);60 }6162 return newtonIteration(this, new Fract(1n));63 }64}6566type Op = Fract | '+' | '-' | '*' | '/' | 'dup' | Op[];67function rpn(...ops: (Op)[]) {68 const stack: Fract[] = [];69 for (const op of ops) {70 if (op instanceof Fract) {71 stack.push(op);72 } else if (op === '+') {73 if (stack.length < 2)74 throw new Error('stack underflow');75 const a = stack.pop()!;76 const b = stack.pop()!;77 stack.push(a.plus(b));78 } else if (op === '*') {79 if (stack.length < 2)80 throw new Error('stack underflow');81 const a = stack.pop()!;82 const b = stack.pop()!;83 stack.push(a.mul(b));84 } else if (op === '-') {85 if (stack.length < 2)86 throw new Error('stack underflow');87 const a = stack.pop()!;88 const b = stack.pop()!;89 stack.push(a.minus(b));90 } else if (op === '/') {91 if (stack.length < 2)92 throw new Error('stack underflow');93 const a = stack.pop()!;94 const b = stack.pop()!;95 stack.push(a.div(b));96 } else if (op === 'dup') {97 if (stack.length < 1)98 throw new Error('stack underflow');99 const a = stack.pop()!;100 stack.push(a);101 stack.push(a);102 } else if (Array.isArray(op)) {103 stack.push(rpn(...op));104 } else {105 throw new Error(`unknown operand: ${op}`);106 }107 }108 if (stack.length != 1)109 throw new Error('one element should be left on stack');110 return stack[0]!;111}31124function linearRegression(points: { x: bigint, y: bigint }[]) {113function linearRegression(points: { x: Fract, y: Fract }[]) {5 let sumxy = 0n;114 let sumxy = Fract.ZERO;6 let sumx = 0n;115 let sumx = Fract.ZERO;7 let sumy = 0n;116 let sumy = Fract.ZERO;8 let sumx2 = 0n;117 let sumx2 = Fract.ZERO;9 const n = points.length;118 const n = points.length;10 for (let i = 0; i < n; i++) {119 for (let i = 0; i < n; i++) {11 const p = points[i];120 const p = points[i];12 sumxy += p.x * p.y;121 sumxy = rpn(p.x, p.y, '*', sumxy, '+');13 sumx += p.x;122 sumx = sumx.plus(p.x);14 sumy += p.y;123 sumy = sumy.plus(p.y);15 sumx2 += p.x * p.x;124 sumx2 = rpn(p.x, p.x, '*', sumx2, '+');16 }125 }1712618 const nb = BigInt(n);127 const nb = new Fract(BigInt(n));1920 const precision = 100000000n;2112822 // This is a workaround to beat the lack of precision of the `Number` type.129 // 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.130 // 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.131 // 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.132 // It is crucial to have the correct slope for the regression line.13326 const a = Number((precision * (nb * sumxy - sumx * sumy)) / (nb * sumx2 - sumx * sumx)) / Number(precision);134 const a = rpn(135 [nb, sumxy, '*', sumx, sumy, '*', '-'],136 [nb, sumx, '*', sumx, sumx, '*', '-'],137 '/',138 );27 const b = (Number(sumy) - a * Number(sumx)) / Number(nb);139 const b = rpn(140 [sumy, a, sumx, '*', '-'],141 nb,142 '/',143 );2814429 return {a, b};145 return {a, b};30}146}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 }4214743 function newtonIteration(n: bigint, x0: bigint): bigint {148const hypothesisLinear = (a: Fract, b: Fract) => (x: Fract) => rpn(x, a, '*', b, '+');44 const x1 = ((n / x0) + x0) >> 1n;14945 if (x0 === x1 || x0 === (x1 - 1n)) {150// JS has no builtin function to calculate sqrt of bigint46 return x0;151// https://stackoverflow.com/a/53684036/619016947 }48 return newtonIteration(n, x1);49 }5051 return newtonIteration(value, 1n);52}5354function _error(points: { x: bigint, y: bigint }[], hypothesis: (a: bigint) => bigint) {152function error(points: { x: Fract, y: Fract }[], hypothesis: (a: Fract) => Fract) {55 return sqrt(points.map(p => {153 return points.map(p => {56 const v = hypothesis(p.x);154 const v = hypothesis(p.x);57 const vv = p.y;155 const vv = p.y;5815659 return (v - vv) ** 2n;157 return rpn(v, vv, '-', 'dup', '*');60 }).reduce((a, b) => a + b, 0n) / BigInt(points.length));158 }).reduce((a, b) => a.plus(b), Fract.ZERO).sqrt().div(new Fract(BigInt(points.length)));61}159}6216063async function calibrateWeightToFee(helper: EthUniqueHelper, privateKey: (account: string) => Promise<IKeyringPair>) {161async function calibrateWeightToFee(helper: EthUniqueHelper, privateKey: (account: string) => Promise<IKeyringPair>) {80 for (let i = -5; i < 5; i++) {178 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))));179 await helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.configuration.setWeightToFeeCoefficientOverride(base + base / 1000n * BigInt(i))));8218083 const coefficient = (await api.query.configuration.weightToFeeCoefficientOverride() as any).toBigInt();181 const coefficient = new Fract((await api.query.configuration.weightToFeeCoefficientOverride() as any).toBigInt());84 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'});182 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'});85 const token = await collection.mintToken(alice, {Substrate: alice.address});183 const token = await collection.mintToken(alice, {Substrate: alice.address});8618487 const aliceBalanceBefore = await helper.balance.getSubstrate(alice.address);185 const aliceBalanceBefore = await helper.balance.getSubstrate(alice.address);88 await token.transfer(alice, {Substrate: bob.address});186 await token.transfer(alice, {Substrate: bob.address});89 const aliceBalanceAfter = await helper.balance.getSubstrate(alice.address);187 const aliceBalanceAfter = await helper.balance.getSubstrate(alice.address);9018891 const transferPrice = aliceBalanceBefore - aliceBalanceAfter;189 const transferPrice = new Fract(aliceBalanceBefore - aliceBalanceAfter);9219093 dataPoints.push({x: transferPrice, y: coefficient});191 dataPoints.push({x: transferPrice, y: coefficient});94 }192 }95 const {a, b} = linearRegression(dataPoints);193 const {a, b} = linearRegression(dataPoints);9619497 // console.log(`Error: ${error(dataPoints, x => a*x+b)}`);195 const hyp = hypothesisLinear(a, b);98196 console.log(`Error: ${error(dataPoints, hyp)}`);197198 // 0.1 UNQ99 const perfectValue = BigInt(Math.ceil(a * Number(helper.balance.getOneTokenNominal()) / 10 + b));199 const perfectValue = hyp(rpn(new Fract(helper.balance.getOneTokenNominal()), new Fract(1n, 10n), '*'));100 await helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.configuration.setWeightToFeeCoefficientOverride(perfectValue.toString())));200 await helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.configuration.setWeightToFeeCoefficientOverride(perfectValue.toBigInt())));101201102 {202 {103 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'});203 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'});136 const gasPriceStr = '0x' + gasPrice.toString(16);236 const gasPriceStr = '0x' + gasPrice.toString(16);137 await helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.configuration.setMinGasPriceOverride(gasPrice)));237 await helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.configuration.setMinGasPriceOverride(gasPrice)));138238139 const coefficient = (await api.query.configuration.minGasPriceOverride() as any).toBigInt();239 const coefficient = new Fract((await api.query.configuration.minGasPriceOverride() as any).toBigInt());140 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'});240 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'});141 const token = await collection.mintToken(alice, {Ethereum: caller});241 const token = await collection.mintToken(alice, {Ethereum: caller});142242143 const address = helper.ethAddress.fromCollectionId(collection.collectionId);243 const address = helper.ethAddress.fromCollectionId(collection.collectionId);144 const contract = helper.ethNativeContract.collection(address, 'nft', caller);244 const contract = helper.ethNativeContract.collection(address, 'nft', caller);145245146 const transferPrice = await helper.eth.calculateFee({Ethereum: caller}, () => contract.methods.transfer(receiver, token.tokenId).send({from: caller, gasPrice: gasPriceStr, gas: helper.eth.DEFAULT_GAS}));246 const transferPrice = new Fract(await helper.eth.calculateFee({Ethereum: caller}, () => contract.methods.transfer(receiver, token.tokenId).send({from: caller, gasPrice: gasPriceStr, gas: helper.eth.DEFAULT_GAS})));147247148 dataPoints.push({x: transferPrice, y: coefficient});248 dataPoints.push({x: transferPrice, y: coefficient});149 }249 }150250151 const {a, b} = linearRegression(dataPoints);251 const {a, b} = linearRegression(dataPoints);152252153 // console.log(`Error: ${error(dataPoints, x => a*x+b)}`);253 const hyp = hypothesisLinear(a, b);154254 console.log(`Error: ${error(dataPoints, hyp)}`);155 // * 0.15 = * 10000 / 66666255256 // 0.15 UNQ156 const perfectValue = BigInt(Math.ceil(a * Number(helper.balance.getOneTokenNominal() * 1000000n / 6666666n) + b));257 const perfectValue = hyp(rpn(new Fract(helper.balance.getOneTokenNominal()), new Fract(15n, 100n), '*'));157 await helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.configuration.setMinGasPriceOverride(perfectValue.toString())));258 await helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.configuration.setMinGasPriceOverride(perfectValue.toBigInt())));158259159 {260 {160 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'});261 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'});