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 23 24 25 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}31323334function 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 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 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 154155 156 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 175176 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})();