1import {IKeyringPair} from '@polkadot/types/types';23import {usingEthPlaygrounds, EthUniqueHelper} from './eth/util/playgrounds';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}27282930function 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) => IKeyringPair) {60 const alice = privateKey('//Alice');61 const bob = 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 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) => IKeyringPair) {110 const alice = 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 149150 151 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 170171 await calibrateWeightToFee(helper, privateKey);172 await calibrateWeightToFee(helper, privateKey);173174 await calibrateMinGasPrice(helper, privateKey);175 await calibrateMinGasPrice(helper, privateKey);176 });177})();