diff --git a/.github/workflows/verify-compilation.yml b/.github/workflows/verify-compilation.yml index 1c33242..221e495 100644 --- a/.github/workflows/verify-compilation.yml +++ b/.github/workflows/verify-compilation.yml @@ -13,10 +13,16 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v2 with: - node-version: "14" + node-version: "20" - name: Install dependencies run: npm install + - name: Setup bun + uses: oven-sh/setup-bun@v2 + - name: Verify TypeScript compilation - run: npm run build + run: bun run build + + - name: Run tests + run: bun run test diff --git a/README.md b/README.md index e4c2512..6ba0deb 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,29 @@ # moonbeam-tools -Tools related to Moonbeam blockchains +Tools related to Moonbeam blockchains. -# Requirements +## Requirements -* NodeJS v14+ +* bun v1+ -# Tools +## Actions +__Installation__: `npm install` +__Test__: `bun run test` +__Build__: `bun run build` +__Publish__: `bun publish` -## Installation +## Debug -``` -sudo npm install -g moonbeam-tools@latest -``` +You can use `DEBUG=helper:*` for logs on the state manipation + +# Tools -## Running moonbeam-monitor +## Monitoring chains Allows to monitor a Moonbeam network. To do so, run the following command: ``` -moonbeam-monitor --networks moonriver +bunx moonbeam-tools --networks moonbeam moonriver ``` ``` @@ -40,7 +44,7 @@ Options: Monitoring - + + + + + + +`, +); +// editorconfig-checker-enable + +const openCmd = (() => { + switch (process.platform) { + case "darwin": + return "open"; + case "win32": + return "start"; + default: + return "xdg-open"; + } +})(); + +exec(`${openCmd} ${outputFile}`); diff --git a/src/tools/fees.ts b/src/tools/fees.ts new file mode 100644 index 0000000..b2daeb6 --- /dev/null +++ b/src/tools/fees.ts @@ -0,0 +1,1120 @@ +import { exec as execProcess } from "child_process"; +import fs from "fs"; +import assert from "node:assert/strict"; +import util from "node:util"; +import { setTimeout } from "timers/promises"; + +import "@moonbeam-network/api-augment"; +import { ApiPromise, WsProvider } from "@polkadot/api"; +import "@polkadot/api-augment"; +import { SubmittableExtrinsic as SubmittableExtrinsicPromise } from "@polkadot/api/promise/types"; +import { ApiTypes, SubmittableExtrinsic } from "@polkadot/api/types"; +import Keyring from "@polkadot/keyring"; +import { DispatchError, EventRecord } from "@polkadot/types/interfaces"; +import { EvmCoreErrorExitReason } from "@polkadot/types/lookup"; +import { BN, u8aToHex } from "@polkadot/util"; +import { ethers } from "ethers"; +import { AccessListish } from "ethers/lib/utils.js"; +import * as RLP from "rlp"; +import solc from "solc"; +import { JsonRpcResponseWithResult, Web3 } from "web3"; +import { Contract } from "web3-eth-contract"; +import yargs from "yargs"; +import { + ALITH_PRIVATE_KEY, + BALTATHAR_ADDRESS, + BALTATHAR_PRIVATE_KEY, + CHARLETH_ADDRESS, + CHARLETH_PRIVATE_KEY, + DOROTHY_PRIVATE_KEY, +} from "../utils/constants.ts"; + +const httpUrl = "http://127.0.0.1:9933"; +const wssUrl = "ws://127.0.0.1:9944"; + +const exec = util.promisify(execProcess); +const ethersApi = new ethers.providers.JsonRpcProvider(httpUrl); +const keyringEth = new Keyring({ type: "ethereum" }); +export const alith = keyringEth.addFromUri(ALITH_PRIVATE_KEY); +export const baltathar = keyringEth.addFromUri(BALTATHAR_PRIVATE_KEY); +export const charleth = keyringEth.addFromUri(CHARLETH_PRIVATE_KEY); +export const dorothy = keyringEth.addFromUri(DOROTHY_PRIVATE_KEY); +const web3 = new Web3(wssUrl); +web3.eth.accounts.wallet.add(ALITH_PRIVATE_KEY); + +/** + * This test assumes the following: + * + moonbeam + * - EVM calls are unfiltered. + * NormalFilter - Call::EVM(_) => true + * - EVM origin is allowed for all. + * type CallOrigin = EnsureAddressAlways; + * + * pub struct EnsureAddressAlways; + * impl EnsureAddressOrigin for EnsureAddressAlways { + * type Success = (); + * + * fn try_address_origin( + * _address: &H160, + * _origin: OuterOrigin, + * ) -> Result { + * Ok(()) + * } + * + * fn ensure_address_origin( + * _address: &H160, + * _origin: OuterOrigin, + * ) -> Result { + * Ok(()) + * } + * } + * + frontier + * - Baltathar pays no EVM fees and full substrate fees, while Charleth pays the opposite. + * let baltathar_addr = H160::from_str("0x3cd0a705a2dc65e5b1e1205896baa2be8a07c6e0").unwrap(); + * let (validate, payable) = if source == baltathar_addr { + * (false, Pays::Yes) + * } else { + * (true, Pays::No) + * }; + * + * Then start the node with the following command + * ./target/release/moonbeam \ + * --execution=Native \ + * --wasm-execution=interpreted-i-know-what-i-do \ + * --ethapi=txpool \ + * --no-hardware-benchmarks \ + * --no-telemetry \ + * --no-prometheus \ + * --force-authoring \ + * --rpc-cors=all \ + * --alice \ + * --chain=moonbase-dev \ + * --sealing=manual \ + * --in-peers=0 \ + * --out-peers=0 -linfo \ + * --tmp + * + * Examples: + * ts-node ./src/tools/fees.ts --name fees --type compute + * ts-node ./src/tools/fees.ts --name fees --type compute --multiplier 300000000 + * ts-node ./src/tools/fees.ts --name fees --type length-small + * ts-node ./src/tools/fees.ts --name fees --type length-big + * + * The result will open in the browser once done + */ + +/** + * Observations + * - The first EVM call causes the SmartContract storage to be initialized and costs around 20,000 gas (we avoid this by pre-initializing the storage) + * - The fees sometime jump abruptly and stay at that level, this is due to nonce going from 1 byte to 2 bytes and so on + * - The block fill ratio is computed differently by transaction-payment; the multiplier is updated only due to `Normal` weight class (actual / max). + * The actual weight also contains some extra weight that doesn't belong to extrinsics (maybe coming from on_initialize/on_finalize) + */ + +/// === test methods === /// + +const TESTER_CONTRACT = `// SPDX-License-Identifier: GPL-3.0-only + pragma solidity >=0.8.3; + + contract Tester { + + uint256 public count; + + function infinite() public pure { + while (true) {} + } + + function incrementalLoop(uint256 n) public { + uint256 i = 0; + while (i < n) { + count = count + 1; + i += 1; + } + } + + function bigData(bytes memory b) public { + // do nothing + } + }`; +const TESTER_JSON = compileSolidity(TESTER_CONTRACT); +const TESTER_INTERFACE = new ethers.utils.Interface(TESTER_JSON.contract.abi); + +async function runTest( + api: ApiPromise, + options: { callType: "compute" | "length-small" | "length-big"; multiplier: BN | null }, +) { + const result = []; + console.log(`options: ${JSON.stringify(options)}`); + let contractAddr = "0xc01Ee7f10EA4aF4673cFff62710E1D7792aBa8f3"; + + // deploy contract + const maxBlockWeight = api.consts.system.blockWeights.maxBlock.refTime.toBn(); + const blockNumber = (await api.rpc.chain.getBlock()).block.header.number.toNumber(); + const nextFeeMultiplierOriginal = await api.query.transactionPayment.nextFeeMultiplier(); + if (blockNumber === 0) { + const { contract, rawTx } = await createContract(TESTER_JSON, { + ...ALITH_TRANSACTION_TEMPLATE, + gas: 900_000, + gasPrice: 1_250_000_000, + }); + + const results = await createBlock(api, rawTx); + assert.equal(results, true, "failure during block creation"); + await expectEVMSuccess(api); + console.log(`contractAddress: ${contract.options.address.toString()}`); + contractAddr = contract.options.address; + } + + // use the specified call type + const contractCall = (() => { + switch (options.callType) { + case "compute": + return TESTER_INTERFACE.encodeFunctionData("incrementalLoop", [10]); + case "length-small": + return TESTER_INTERFACE.encodeFunctionData("bigData", [new Array(100).fill(0x01)]); + case "length-big": + return TESTER_INTERFACE.encodeFunctionData("bigData", [new Array(50 * 1024).fill(0x01)]); + default: + throw new Error(`invalid options.callType ${options.callType}`); + } + })(); + + // init the smart contract storage, if not done then the first tx has a storage initialization cost of around 20,000 + await createBlock(api, [ + await api.tx.evm + .call( + dorothy.address, + contractAddr, + contractCall, + 0, + 900_000n, + 2_000_000_000n, + null, + null, + [], + ) + .signAsync(dorothy), + ]); + + // override nextFeeMultiplier if needed, note that its value will change immediately after block creation + const nextFeeMultiplierOverride = options.multiplier || nextFeeMultiplierOriginal; + if (nextFeeMultiplierOverride) { + console.log(`overriding nextFeeMultiplier to ${nextFeeMultiplierOverride}`); + await createBlock(api, [ + await api.tx.balances.transfer(contractAddr, 0).signAsync(alith), + await api.tx.sudo + .sudo( + await api.tx.system + .setStorage([ + [ + "0x3f1467a096bcd71a5b6a0c8155e208103f2edf3bdf381debe331ab7446addfdc", + u8aToHex(api.createType("u128", nextFeeMultiplierOverride).toU8a()), + ], + ]) + .signAsync(alith), + ) + .signAsync(alith), + ]); + } + + // start load test + // const loadFactors = [...generateLoad(60, 1), ...Array(10).fill(0)]; + // const loadFactors = [...Array(183).fill(19)]; + const loadFactors = [...Array(183).fill(55)]; + // const loadFactors = [...Array(1).fill(0)]; + const repsPerLoad = 30; + for await (const [loadFactorIndex, loadFactor] of loadFactors.entries()) { + console.log( + `load: ${loadFactor} (${repsPerLoad} reps) ${loadFactorIndex + 1}/${loadFactors.length}`, + ); + for await (const rep of new Array(repsPerLoad).keys()) { + // uncomment the following code to reduce feeMultiplier by 10 each 100 blocks + // if (blockN % 100 === 0) { + // console.log(`feeMultiplier ${feeMultiplier.toString()}`); + // await createBlock(api, [ + // await api.tx.sudo + // .sudo( + // await api.tx.system + // .setStorage([ + // [ + // "0x3f1467a096bcd71a5b6a0c8155e208103f2edf3bdf381debe331ab7446addfdc", + // u8aToHex(api.createType("u128", feeMultiplier).toU8a()), + // ], + // ]) + // .signAsync(alith) + // ) + // .signAsync(alith), + // ]); + // if (feeMultiplier.eqn(1)) { + // feeMultiplier = BN_ZERO; + // break; + // } + // feeMultiplier = feeMultiplier.divn(10); + // } + // blockN++; + + const multiplierBefore = await api.query.transactionPayment.nextFeeMultiplier(); + const fees = await txObserveFeeDiff(api, async () => { + const txs = [ + // fill block + await api.tx.sudo + .sudo(await api.tx.system.fillBlock(loadFactor * 10_000_000).signAsync(alith)) + .signAsync(alith), + + // charge substrate fees + await api.tx.evm + .call(baltathar.address, contractAddr, contractCall, 0, 11_000_000n, 0n, null, null, []) + .signAsync(baltathar, { tip: 1n * 10n ** 15n }), + + // charge EVM fees + await api.tx.evm + .call( + charleth.address, + contractAddr, + contractCall, + 0, + 11_000_000n, + 2_000_000_000_000_000n, + null, + null, + [], + ) + .signAsync(charleth, { tip: 1n * 10n ** 15n }), + ]; + + txs.forEach((t) => { + console.log(t.hash.toString()); + }); + + return txs; + }); + + // get block details + const transactions = { + substrate: null, + evm: null, + }; + const block = await api.rpc.chain.getBlock(); + for (const [i, ext] of block.block.extrinsics.entries()) { + if (ext.signer.eq(BALTATHAR_ADDRESS)) { + transactions.substrate = { + index: i, + extrinsicLength: ext.encodedLength, + extrinsic: ext, + }; + } else if (ext.signer.eq(CHARLETH_ADDRESS)) { + transactions.evm = { + index: i, + extrinsicLength: ext.encodedLength, + extrinsic: ext, + }; + } + } + + // compute block weight, from events + const weights = {}; + const events = await api.query.system.events(); + let totalBlockWeight = new BN(0); + for (const { phase, event } of events) { + if (phase.isApplyExtrinsic) { + if ( + api.events.system.ExtrinsicSuccess.is(event) || + api.events.system.ExtrinsicFailed.is(event) + ) { + weights[phase.asApplyExtrinsic.toNumber()] = + event.data.dispatchInfo.weight.refTime.toBn(); + } + } + } + if (!transactions.substrate || transactions.evm) { + } + for (const i of Object.keys(weights)) { + const key = parseInt(i); + if (transactions.substrate && transactions.substrate.index === key) { + transactions.substrate.weight = weights[i].toString(); + } else if (transactions.evm && transactions.evm.index === key) { + transactions.evm.weight = weights[i].toString(); + } + switch (parseInt(i)) { + case transactions.substrate.index: + transactions.substrate.weight = weights[i].toString(); + break; + case transactions.evm.index: + transactions.evm.weight = weights[i].toString(); + break; + } + totalBlockWeight = totalBlockWeight.add(weights[i]); + } + + // get feeDetails + + const feeDetails = ( + await api.rpc.payment.queryFeeDetails(transactions.substrate.extrinsic.toHex()) + ).inclusionFee.unwrap(); + const supplyFactor = 1; // 100 for moonbeam, 1 otherwise + const substrateFeeDetails = { + baseFee: feeDetails.baseFee.toString(), + lengthFee: feeDetails.lenFee.toString(), + adjustedWeightFee: multiplierBefore + .mul(new BN(transactions.substrate.weight).muln(50_000 * supplyFactor)) + .div(new BN("1000000000000000000")) + .toString(), + total: null, + }; + substrateFeeDetails.total = Object.values(substrateFeeDetails) + .reduce((acc, v) => acc.add(new BN(v)), new BN(0)) + .toString(); + + const multiplierAfter = await api.query.transactionPayment.nextFeeMultiplier(); + + delete transactions.substrate.extrinsic; + delete transactions.evm.extrinsic; + const data = { + fullPercent: totalBlockWeight.muln(100).div(maxBlockWeight).toNumber(), + ...fees, + transactions, + substrateFeeDetails, + multiplier: { + before: multiplierBefore.toString(), + after: multiplierAfter.toString(), + }, + block: (await api.rpc.chain.getBlock()).block.header.number.toNumber(), + }; + result.push(data); + if (data.block === 4) { + throw Error("FOUR!"); + } + } + } + + return { + multiplier: nextFeeMultiplierOverride.toString(), + callType: options.callType, + result, + }; +} + +function generateLoad(middle: number, inc: number = 1): number[] { + const load = []; + for (let i = 0; i <= middle; i += inc) { + load.push(i); + } + for (let i = 0; i <= 50; i++) { + load.push(middle); + } + for (let i = middle; i >= 0; i -= inc) { + load.push(i); + } + + return load; +} + +async function txObserveFeeDiff( + api: ApiPromise, + txFunc: () => Promise, +) { + const txs = await txFunc(); + const balanceBeforeBaltathar = await api.query.system.account(BALTATHAR_ADDRESS); + const balanceBeforeCharleth = await api.query.system.account(CHARLETH_ADDRESS); + await createBlock(api, txs); + const balanceAfterBaltathar = await api.query.system.account(BALTATHAR_ADDRESS); + const balanceAfterCharleth = await api.query.system.account(CHARLETH_ADDRESS); + + return { + substrate: balanceBeforeBaltathar.data.free.sub(balanceAfterBaltathar.data.free).toString(), + evm: balanceBeforeCharleth.data.free.sub(balanceAfterCharleth.data.free).toString(), + }; +} + +/// === block creation methods === /// + +async function expectEVMSuccess(api: ApiPromise) { + const events = await api.query.system.events(); + const ethereumResult = events.find( + ({ event: { section, method } }) => section == "ethereum" && method == "Executed", + ).event.data[3] as EvmCoreErrorExitReason; + assert.equal(ethereumResult.isSucceed, true, "EVM operation failed"); +} + +function extractError(events: EventRecord[] = []): DispatchError | undefined { + return events + .filter(({ event }) => "system" === event.section && ["ExtrinsicFailed"].includes(event.method)) + .map( + ({ + event: { + data: [dispatchError], + }, + }) => dispatchError as DispatchError, + )[0]; +} + +async function customWeb3Request(web3: Web3, method: string, params: any[]) { + return new Promise((resolve, reject) => { + (web3.currentProvider as any).send( + { + jsonrpc: "2.0", + id: 1, + method, + params, + }, + (error: Error | null, result?: JsonRpcResponseWithResult) => { + if (error) { + reject( + `Failed to send custom request (${method} (${params + .map((p) => { + const str = p.toString(); + return str.length > 128 ? `${str.slice(0, 96)}...${str.slice(-28)}` : str; + }) + .join(",")})): ${error.message || error.toString()}`, + ); + } + resolve(result); + }, + ); + }); +} + +interface BlockCreation { + parentHash?: string; + finalize?: boolean; +} +type ExtrinsicCreation = boolean; +async function createBlock< + ApiType extends ApiTypes, + Call extends + | SubmittableExtrinsic + | Promise> + | string + | Promise, + Calls extends Call | Call[], +>(api: ApiPromise, transactions?: Calls, options: BlockCreation = {}) { + const results: ({ type: "eth"; hash: string } | { type: "sub"; hash: string })[] = []; + const txs = + transactions == undefined ? [] : Array.isArray(transactions) ? transactions : [transactions]; + for await (const call of txs) { + if (typeof call == "string") { + // Ethereum + results.push({ + type: "eth", + hash: (await customWeb3Request(web3, "eth_sendRawTransaction", [call])).result.toString(), + }); + } else if (call.isSigned) { + results.push({ + type: "sub", + hash: (await call.send()).toString(), + }); + } else { + results.push({ + type: "sub", + hash: (await call.signAndSend(alith)).toString(), + }); + } + } + + const { parentHash, finalize } = options; + const block = parentHash + ? await api.rpc.engine.createBlock(true, finalize, parentHash) + : await api.rpc.engine.createBlock(true, finalize); + const blockHash = block.get("hash").toString(); + + // No need to extract events if no transactions + if (results.length == 0) { + return { + block, + result: null, + }; + } + + // We retrieve the events for that block + const allRecords: EventRecord[] = (await (await api.at(blockHash)).query.system.events()) as any; + // We retrieve the block (including the extrinsics) + const blockData = await api.rpc.chain.getBlock(blockHash); + + const result: ExtrinsicCreation[] = results.map((result) => { + const extrinsicIndex = + result.type == "eth" + ? allRecords + .find( + ({ phase, event: { section, method, data } }) => + phase.isApplyExtrinsic && + section == "ethereum" && + method == "Executed" && + data[2].toString() == result.hash, + ) + ?.phase?.asApplyExtrinsic?.toNumber() + : blockData.block.extrinsics.findIndex((ext) => ext.hash.toHex() == result.hash); + // We retrieve the events associated with the extrinsic + const events = allRecords.filter( + ({ phase }) => phase.isApplyExtrinsic && phase.asApplyExtrinsic.toNumber() === extrinsicIndex, + ); + const failure = extractError(events); + const successful = extrinsicIndex !== undefined && !failure; + return successful; + }); + + // Adds extra time to avoid empty transaction when querying it + if (results.find((r) => r.type == "eth")) { + await setTimeout(2); + } + + return Array.isArray(transactions) ? result : (result[0] as boolean); +} + +const ALITH_TRANSACTION_TEMPLATE: TransactionOptions = { + from: alith.address, + privateKey: ALITH_PRIVATE_KEY, + nonce: null, + gas: 500_000, + gasPrice: 1_000_000_000, + value: "0x00", +}; +interface TransactionOptions { + from?: string; + to?: string; + privateKey?: string; + nonce?: number; + gas?: string | number; + gasPrice?: string | number; + maxFeePerGas?: string | number; + maxPriorityFeePerGas?: string | number; + value?: string | number; + data?: string; + accessList?: AccessListish; // AccessList | Array<[string, Array]> +} +async function createTransaction( + options: TransactionOptions, + ethTransactionType = "Legacy", +): Promise { + const isLegacy = ethTransactionType === "Legacy"; + const isEip2930 = ethTransactionType === "EIP2930"; + const isEip1559 = ethTransactionType === "EIP1559"; + + const gasPrice = options.gasPrice !== undefined ? options.gasPrice : 1_000_000_000; + const maxPriorityFeePerGas = + options.maxPriorityFeePerGas !== undefined ? options.maxPriorityFeePerGas : 0; + const value = options.value !== undefined ? options.value : "0x00"; + const from = options.from || alith.address; + const privateKey = options.privateKey !== undefined ? options.privateKey : ALITH_PRIVATE_KEY; + + // Instead of hardcoding the gas limit, we estimate the gas + const gas = + options.gas || + (await web3.eth.estimateGas({ + from: from, + to: options.to, + data: options.data, + })); + + const maxFeePerGas = options.maxFeePerGas || 1_000_000_000; + const accessList = options.accessList || []; + const nonce = + options.nonce != null ? options.nonce : await web3.eth.getTransactionCount(from, "pending"); + + let data, rawTransaction; + if (isLegacy) { + data = { + from, + to: options.to, + value: value && value.toString(), + gasPrice, + gas, + nonce: nonce, + data: options.data, + }; + const tx = await web3.eth.accounts.signTransaction(data, privateKey); + rawTransaction = tx.rawTransaction; + } else { + const signer = new ethers.Wallet(privateKey, ethersApi); + const chainId = await web3.eth.getChainId(); + if (isEip2930) { + data = { + from, + to: options.to, + value: value && value.toString(), + gasPrice, + gasLimit: gas, + nonce: nonce, + data: options.data, + accessList, + chainId, + type: 1, + }; + } else if (isEip1559) { + data = { + from, + to: options.to, + value: value && value.toString(), + maxFeePerGas, + maxPriorityFeePerGas, + gasLimit: gas, + nonce: nonce, + data: options.data, + accessList, + chainId, + type: 2, + }; + } + rawTransaction = await signer.signTransaction(data); + } + + return rawTransaction; +} + +async function createContract( + contractCompiled: Compiled, + options: TransactionOptions = ALITH_TRANSACTION_TEMPLATE, + contractArguments: any[] = [], +): Promise<{ rawTx: string; contract: Contract; contractAddress: string }> { + const from = options.from !== undefined ? options.from : alith.address; + const nonce = options.nonce || Number(await web3.eth.getTransactionCount(from)); + + const contractAddress = + "0x" + + web3.utils + .sha3(RLP.encode([from, nonce]) as any) + .slice(12) + .substring(14); + + const contract = new web3.eth.Contract(contractCompiled.contract.abi, contractAddress); + const data = contract + .deploy({ + data: contractCompiled.byteCode, + arguments: contractArguments, + }) + .encodeABI(); + + const rawTx = await createTransaction({ ...options, from, nonce, data }); + + return { + rawTx, + contract, + contractAddress, + }; +} + +/// === solidity compile methods === /// + +export interface Compiled { + byteCode: string; + contract: any; + sourceCode: string; +} +function compileSolidity(fileContents: string): Compiled { + // const fileContents = fs.readFileSync(filepath).toString(); + const result = JSON.parse( + solc.compile( + JSON.stringify({ + language: "Solidity", + sources: { + "main.sol": { + content: fileContents, + }, + }, + settings: { + outputSelection: { + "*": { + "*": ["*"], + }, + }, + }, + }), + { + import: (_: string) => { + return { error: "imports not supported" }; + }, + }, + ), + ); + if (!result.contracts) { + throw result; + } + const allContractNames = Object.keys(result.contracts["main.sol"]); + const reduced = allContractNames.reduce((p, contractName) => { + p[contractName] = { + byteCode: "0x" + result.contracts["main.sol"][contractName].evm.bytecode.object, + contract: result.contracts["main.sol"][contractName], + sourceCode: fileContents, + }; + return p; + }, {}); + return reduced[allContractNames[0]]; +} + +/// === main === /// + +async function view(input: string, output: string, open: boolean) { + const data = JSON.parse(fs.readFileSync(input).toString("utf-8")); + const labels = data.result.map((x: any) => x["block"]); + const fullPercent = data.result.map((x: any) => x["fullPercent"]); + const substrateFees = data.result.map((x: any) => new BN(x["substrate"]).toString()); + const evmFees = data.result.map((x: any) => new BN(x["evm"]).toString()); + const multiplier = data.result.map((x: any) => new BN(x["multiplier"]["before"]).toString()); + const diff = data.result.map((x: any) => { + const a = new BN(x["substrate"]); + const b = new BN(x["evm"]); + return a.sub(b).abs().muln(100).div(a.add(b).divn(2)).toString(); + }); + const diffSubstrate = data.result.map((x: any) => { + const a = new BN(x["substrate"]); + const b = new BN(x["evm"]); + return a.sub(b).toString(); + }); + + // editorconfig-checker-disable + fs.writeFileSync( + output, + ` + + + + + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + + + `, + ); + // editorconfig-checker-enable + + const openCmd = (() => { + switch (process.platform) { + case "darwin": + return "open"; + case "win32": + return "start"; + default: + return "xdg-open"; + } + })(); + + if (open) { + await exec(`${openCmd} ${output}`); + } +} + +const argv = yargs(process.argv.slice(2)) + .usage("Usage: $0") + .version("1.0.0") + .options({ + name: { + type: "string", + description: "The output file name", + demandOption: true, + }, + multiplier: { + type: "string", + description: "The multiplier override", + default: "", + }, + view: { + type: "boolean", + description: "View existing file", + }, + type: { + type: "string", + description: "View existing file", + choices: ["compute", "length-small", "length-big"], + demandOption: false, + default: "compute", + }, + }).argv; + +async function main() { + const name = `${argv.name}-${argv.type}`; + if (argv.view) { + await view(`${name}.json`, `${name}.html`, true); + return; + } + + const api = await ApiPromise.create({ + initWasm: false, + provider: new WsProvider(wssUrl), + }); + + try { + const results = await runTest(api, { + callType: argv.type as any, + multiplier: argv.multiplier.length === 0 ? null : new BN(argv.multiplier), + }); + fs.writeFileSync(`${name}.json`, JSON.stringify(results, null, 2)); + await view(`${name}.json`, `${name}.html`, true); + } finally { + await api.disconnect(); + } +} + +main() + .catch((err) => console.error("ERR!", err)) + .finally(() => process.exit(0)); diff --git a/src/tools/flood-evm-compute.ts b/src/tools/flood-evm-compute.ts index e797e05..daacb5c 100644 --- a/src/tools/flood-evm-compute.ts +++ b/src/tools/flood-evm-compute.ts @@ -1,14 +1,13 @@ // This script is expected to run against a parachain network (using launch.ts script) - -import { ALITH_PRIVATE_KEY } from "../utils/constants"; -import { compileSolidity } from "../utils/web3/solidity"; import { Keyring } from "@polkadot/api"; - import * as rlp from "rlp"; +import { Web3 } from "web3"; import yargs from "yargs"; -import { getMonitoredApiFor, NETWORK_YARGS_OPTIONS } from "../utils/networks"; -import Web3 from "web3"; -import { callContract, deployContract } from "../utils/web3/contracts"; + +import { ALITH_PRIVATE_KEY } from "../utils/constants.ts"; +import { getMonitoredApiFor, NETWORK_YARGS_OPTIONS } from "../utils/networks.ts"; +import { callContract, deployContract } from "../utils/web3/contracts.ts"; +import { compileSolidity } from "../utils/web3/solidity.ts"; const argv = yargs(process.argv.slice(2)) .usage("Usage: $0") diff --git a/src/tools/flood-evm-transfers.ts b/src/tools/flood-evm-transfers.ts index 0ee6f31..320a90f 100644 --- a/src/tools/flood-evm-transfers.ts +++ b/src/tools/flood-evm-transfers.ts @@ -1,13 +1,11 @@ // This script is expected to run against a parachain network (using launch.ts script) - -import { ALITH_PRIVATE_KEY } from "../utils/constants"; import { Keyring } from "@polkadot/api"; -import { TransactionReceipt } from "web3-core"; - +import { TransactionReceipt, Web3 } from "web3"; import yargs from "yargs"; -import { getMonitoredApiFor, NETWORK_YARGS_OPTIONS } from "../utils/networks"; -import Web3 from "web3"; -import { customWeb3Request } from "../utils/web3/transactions"; + +import { ALITH_PRIVATE_KEY } from "../utils/constants.ts"; +import { getMonitoredApiFor, NETWORK_YARGS_OPTIONS } from "../utils/networks.ts"; +import { customWeb3Request } from "../utils/web3/transactions.ts"; const argv = yargs(process.argv.slice(2)) .usage("Usage: $0") diff --git a/src/tools/flood-remarks.ts b/src/tools/flood-remarks.ts index 6c617a6..7d4511d 100644 --- a/src/tools/flood-remarks.ts +++ b/src/tools/flood-remarks.ts @@ -1,13 +1,11 @@ // This script is expected to run against a parachain network (using launch.ts script) - -import { ALITH_PRIVATE_KEY } from "../utils/constants"; import { Keyring } from "@polkadot/api"; -import { TransactionReceipt } from "web3-core"; - +import { TransactionReceipt, Web3 } from "web3"; import yargs from "yargs"; -import { getMonitoredApiFor, NETWORK_YARGS_OPTIONS } from "../utils/networks"; -import Web3 from "web3"; -import { customWeb3Request } from "../utils/web3/transactions"; + +import { ALITH_PRIVATE_KEY } from "../utils/constants.ts"; +import { getMonitoredApiFor, NETWORK_YARGS_OPTIONS } from "../utils/networks.ts"; +import { customWeb3Request } from "../utils/web3/transactions.ts"; const argv = yargs(process.argv.slice(2)) .usage("Usage: $0") diff --git a/src/tools/fork-exported-state.ts b/src/tools/fork-exported-state.ts index 64979fa..f39950b 100644 --- a/src/tools/fork-exported-state.ts +++ b/src/tools/fork-exported-state.ts @@ -1,12 +1,11 @@ // This script is expected to run against a parachain network (using launch.ts script) - -import fs from "node:fs/promises"; import yargs from "yargs"; + import { downloadExportedState, NetworkName, neutralizeExportedState, -} from "../libs/helpers/state-manipulator"; +} from "../libs/helpers/state-manipulator/index.ts"; const argv = yargs(process.argv.slice(2)) .usage("Usage: $0") diff --git a/src/tools/get-parachains-xcm-versions.ts b/src/tools/get-parachains-xcm-versions.ts index c4e8ea8..f1a1eea 100644 --- a/src/tools/get-parachains-xcm-versions.ts +++ b/src/tools/get-parachains-xcm-versions.ts @@ -1,4 +1,5 @@ import "@polkadot/api-augment/kusama"; + import { ApiPromise, WsProvider } from "@polkadot/api"; import { prodParasKusama, @@ -7,7 +8,6 @@ import { prodParasPolkadotCommon, } from "@polkadot/apps-config"; import yargs from "yargs"; -import fs from "fs"; export const NETWORK_WS_URLS: { [name: string]: string } = { rococo: "wss://rococo-rpc.polkadot.io", diff --git a/src/tools/get-relay-runtime.ts b/src/tools/get-relay-runtime.ts index b1fe9c9..dfc11c5 100644 --- a/src/tools/get-relay-runtime.ts +++ b/src/tools/get-relay-runtime.ts @@ -1,6 +1,6 @@ import { ApiPromise, WsProvider } from "@polkadot/api"; -import yargs from "yargs"; import fs from "fs"; +import yargs from "yargs"; export const NETWORK_WS_URLS: { [name: string]: string } = { rococo: "wss://rococo-rpc.polkadot.io", diff --git a/src/tools/get-transaction-data.ts b/src/tools/get-transaction-data.ts new file mode 100644 index 0000000..a49579a --- /dev/null +++ b/src/tools/get-transaction-data.ts @@ -0,0 +1,39 @@ +import { ApiPromise, WsProvider } from "@polkadot/api"; +import fs from "fs"; +import { getApiFor, NETWORK_YARGS_OPTIONS } from "src/utils/networks.ts"; +import yargs from "yargs"; + +export const NETWORK_WS_URLS: { [name: string]: string } = { + rococo: "wss://rococo-rpc.polkadot.io", + westend: "wss://westend.api.onfinality.io/public-ws", + kusama: "wss://kusama.api.onfinality.io/public-ws", + polkadot: "wss://polkadot.api.onfinality.io/public-ws", +}; + +const argv = yargs(process.argv.slice(2)) + .usage("Usage: $0") + .version("1.0.0") + .options({ + ...NETWORK_YARGS_OPTIONS, + at: { + type: "number", + description: "Block number", + } + }).argv; + +const main = async () => { + const api = await getApiFor(argv); + + const blockHash = argv.at + ? await api.rpc.chain.getBlockHash(argv.at) + : await api.rpc.chain.getBlockHash(); + const block = await api.rpc.chain.getBlock(blockHash); + + block.block.extrinsics.forEach((ex, index) => { + console.log(index, `${ex.method.section.toString()}.${ex.method.method.toString()} [${ex.hash.toHex()}]\n${ex.method.method.toString() == "setValidationData" ? "..." : ex.toHex()}`); + }); + + await api.disconnect(); +}; + +main(); diff --git a/src/tools/kill-prefix-delegators.ts b/src/tools/kill-prefix-delegators.ts index 0d37e05..722a173 100644 --- a/src/tools/kill-prefix-delegators.ts +++ b/src/tools/kill-prefix-delegators.ts @@ -1,11 +1,10 @@ -import { xxhashAsU8a, blake2AsU8a, blake2AsHex } from "@polkadot/util-crypto"; -import { u8aConcat } from "@polkadot/util"; -import yargs from "yargs"; -import { PalletDemocracyReferendumInfo } from "@polkadot/types/lookup"; -import { getApiFor, NETWORK_YARGS_OPTIONS } from "../utils/networks"; -import chalk from "chalk"; import { Keyring } from "@polkadot/api"; -import { ALITH_PRIVATE_KEY } from "../utils/constants"; +import { PalletDemocracyReferendumInfo } from "@polkadot/types/lookup"; +import { blake2AsHex } from "@polkadot/util-crypto"; +import yargs from "yargs"; + +import { ALITH_PRIVATE_KEY } from "../utils/constants.ts"; +import { getApiFor, NETWORK_YARGS_OPTIONS } from "../utils/networks.ts"; const debug = require("debug")("main"); diff --git a/src/tools/list-active-evm-accounts.ts b/src/tools/list-active-evm-accounts.ts index 0479a43..edc42b5 100644 --- a/src/tools/list-active-evm-accounts.ts +++ b/src/tools/list-active-evm-accounts.ts @@ -1,8 +1,8 @@ +import { ApiPromise } from "@polkadot/api"; import yargs from "yargs"; -import fs from "fs"; -import { exploreBlockRange, getApiFor, NETWORK_YARGS_OPTIONS, reverseBlocks } from ".."; -import { ApiPromise } from "@polkadot/api"; +import { exploreBlockRange, getApiFor, NETWORK_YARGS_OPTIONS } from "../index.ts"; + const XEN_ADDRESS = "0xb564A5767A00Ee9075cAC561c427643286F8F4E1".toLowerCase(); const argv = yargs(process.argv.slice(2)) diff --git a/src/tools/list-collator-candidates.ts b/src/tools/list-collator-candidates.ts index 7d1bc13..5aeb0c5 100644 --- a/src/tools/list-collator-candidates.ts +++ b/src/tools/list-collator-candidates.ts @@ -1,8 +1,9 @@ // This script is expected to run against a parachain network (using launch.ts script) import "@moonbeam-network/api-augment"; + import chalk from "chalk"; -import yargs from "yargs"; import { table } from "table"; +import yargs from "yargs"; import { combineRequestsPerDelegators, @@ -10,7 +11,7 @@ import { getApiFor, NETWORK_YARGS_OPTIONS, numberWithCommas, -} from ".."; +} from "../index.ts"; const argv = yargs(process.argv.slice(2)) .usage("Usage: $0") diff --git a/src/tools/list-collator-rewards.ts b/src/tools/list-collator-rewards.ts index 1e41307..f0852ad 100644 --- a/src/tools/list-collator-rewards.ts +++ b/src/tools/list-collator-rewards.ts @@ -1,10 +1,9 @@ // This script is expected to run against a parachain network (using launch.ts script) -import yargs from "yargs"; +import { ParachainInherentData } from "@polkadot/types/interfaces"; import fs from "fs"; +import yargs from "yargs"; -import { exploreBlockRange, getApiFor, NETWORK_YARGS_OPTIONS } from ".."; - -import { ParachainInherentData } from "@polkadot/types/interfaces"; +import { exploreBlockRange, getApiFor, NETWORK_YARGS_OPTIONS } from "../index.ts"; const INITIAL_ROUND_BLOCK = { moonbeam: 1200, diff --git a/src/tools/list-delegations.ts b/src/tools/list-delegations.ts index feaf59d..4eb61b0 100644 --- a/src/tools/list-delegations.ts +++ b/src/tools/list-delegations.ts @@ -1,13 +1,13 @@ // This script is expected to run against a parachain network (using launch.ts script) -import yargs from "yargs"; import { table } from "table"; +import yargs from "yargs"; import { + combineRequestsPerDelegators, getAccountIdentity, getApiFor, NETWORK_YARGS_OPTIONS, - combineRequestsPerDelegators, -} from ".."; +} from "../index.ts"; const argv = yargs(process.argv.slice(2)) .usage("Usage: $0") diff --git a/src/tools/list-delegators.ts b/src/tools/list-delegators.ts index f571413..88bc5e6 100644 --- a/src/tools/list-delegators.ts +++ b/src/tools/list-delegators.ts @@ -1,13 +1,13 @@ // This script is expected to run against a parachain network (using launch.ts script) -import yargs from "yargs"; import { table } from "table"; +import yargs from "yargs"; import { + combineRequestsPerDelegators, getAccountIdentity, getApiFor, NETWORK_YARGS_OPTIONS, - combineRequestsPerDelegators, -} from ".."; +} from "../index.ts"; const argv = yargs(process.argv.slice(2)) .usage("Usage: $0") diff --git a/src/tools/list-limbo-balance-accounts.ts b/src/tools/list-limbo-balance-accounts.ts index 53d18c5..e294ad9 100644 --- a/src/tools/list-limbo-balance-accounts.ts +++ b/src/tools/list-limbo-balance-accounts.ts @@ -1,11 +1,11 @@ // This script is expected to run against a parachain network (using launch.ts script) -import yargs from "yargs"; -import { table } from "table"; - -import { getAccountIdentity } from "../utils/monitoring"; -import { getApiFor, NETWORK_YARGS_OPTIONS } from ".."; import { bnMax } from "@polkadot/util"; +import { table } from "table"; import Web3 from "web3"; +import yargs from "yargs"; + +import { getApiFor, NETWORK_YARGS_OPTIONS } from "../index.ts"; +import { getAccountIdentity } from "../utils/monitoring.ts"; const argv = yargs(process.argv.slice(2)) .usage("Usage: $0") @@ -41,10 +41,10 @@ const main = async () => { affectedAccounts.push([ addressesToCheck[idx], identities[idx], - Web3.utils.fromWei(free.toString()), - Web3.utils.fromWei(reserved.toString()), - Web3.utils.fromWei(frozen.toString()), - Web3.utils.fromWei(frozen.sub(free).toString()), + Web3.utils.fromWei(free.toString(), "ether"), + Web3.utils.fromWei(reserved.toString(), "ether"), + Web3.utils.fromWei(frozen.toString(), "ether"), + Web3.utils.fromWei(frozen.sub(free).toString(), "ether"), ]); } }); diff --git a/src/tools/list-methods.ts b/src/tools/list-methods.ts index 1635d27..77cd0dc 100644 --- a/src/tools/list-methods.ts +++ b/src/tools/list-methods.ts @@ -1,6 +1,6 @@ import yargs from "yargs"; -import { getApiFor, NETWORK_YARGS_OPTIONS } from "../utils/networks"; +import { getApiFor, NETWORK_YARGS_OPTIONS } from "../utils/networks.ts"; const argv = yargs(process.argv.slice(2)) .usage("Usage: $0") diff --git a/src/tools/list-precompiles.ts b/src/tools/list-precompiles.ts index f5c94d8..ac365d9 100644 --- a/src/tools/list-precompiles.ts +++ b/src/tools/list-precompiles.ts @@ -1,10 +1,11 @@ -import { xxhashAsU8a, blake2AsU8a } from "@polkadot/util-crypto"; import { u8aConcat } from "@polkadot/util"; -import yargs from "yargs"; -import { getApiFor, getViemAccountFor, getViemFor, NETWORK_YARGS_OPTIONS } from "../utils/networks"; -import { createPublicClient, createWalletClient, encodeFunctionData, http } from "viem"; -import { privateKeyToAccount } from "viem/accounts"; +import { blake2AsU8a, xxhashAsU8a } from "@polkadot/util-crypto"; import chalk from "chalk"; +import { encodeFunctionData } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import yargs from "yargs"; + +import { getViemAccountFor, getViemFor, NETWORK_YARGS_OPTIONS } from "../utils/networks.ts"; const debug = require("debug")("main"); diff --git a/src/tools/list-referendum.ts b/src/tools/list-referendum.ts index c25d953..4402754 100644 --- a/src/tools/list-referendum.ts +++ b/src/tools/list-referendum.ts @@ -1,14 +1,14 @@ +import { ApiPromise } from "@polkadot/api"; +import { BN, BN_TEN } from "@polkadot/util"; import humanizeNumber from "humanize-number"; import { moment } from "moment-parseplus"; import yargs from "yargs"; -import { ApiPromise } from "@polkadot/api"; -import { BN, BN_TEN } from "@polkadot/util"; -import { getBlockDate } from "../utils/block-time"; -import { getApiFor, NETWORK_YARGS_OPTIONS } from "../utils/networks"; -import { getReferendumByGroups } from "../utils/referenda"; -import { callInterpreter, renderCallInterpretation } from "../utils/transactions"; -import { promiseConcurrent } from "../utils/functions"; +import { getBlockDate } from "../utils/block-time.ts"; +import { promiseConcurrent } from "../utils/functions.ts"; +import { getApiFor, NETWORK_YARGS_OPTIONS } from "../utils/networks.ts"; +import { getReferendumByGroups } from "../utils/referenda.ts"; +import { callInterpreter, renderCallInterpretation } from "../utils/transactions.ts"; const argv = yargs(process.argv.slice(2)) .usage("Usage: $0") diff --git a/src/tools/list-storages.ts b/src/tools/list-storages.ts index 174872d..e80c977 100644 --- a/src/tools/list-storages.ts +++ b/src/tools/list-storages.ts @@ -1,6 +1,8 @@ import yargs from "yargs"; +import chalk from "chalk"; -import { getApiFor, NETWORK_YARGS_OPTIONS } from "../utils/networks"; +import { getApiFor, NETWORK_YARGS_OPTIONS } from "../utils/networks.ts"; +import { xxhashAsHex } from "@polkadot/util-crypto"; const argv = yargs(process.argv.slice(2)) .usage("Usage: $0") @@ -9,15 +11,26 @@ const argv = yargs(process.argv.slice(2)) ...NETWORK_YARGS_OPTIONS, }).argv; +const capitalize = (s) => { + return String(s[0]).toUpperCase() + String(s).slice(1); +} const main = async () => { const api = await getApiFor(argv); for (const section of Object.keys(api.query)) { - console.log(`${section}`); + const palletName = section == "evm" ? "EVM" : capitalize(section); + const sectionKey = xxhashAsHex(palletName, 128); + console.log(`${chalk.yellow(palletName)}`); for (const method of Object.keys(api.query[section])) { - console.log( - ` ${`${section}.${method}`.padStart(50, " ")}: ${api.query[section][method].keyPrefix()}`, - ); + if (api.query[section][method].keyPrefix().includes(sectionKey.slice(2))) { + console.log( + ` ${`${section}.${method}`.padStart(50, " ")}: ${chalk.yellow(sectionKey)}${api.query[section][method].keyPrefix().slice(34)}`, + ); + } else { + console.log( + ` ${`${section}.${method}`.padStart(50, " ")}: ${api.query[section][method].keyPrefix()}`, + ); + } } } await api.disconnect(); diff --git a/src/tools/monitor.ts b/src/tools/monitor.ts index f8a62ef..a3af324 100644 --- a/src/tools/monitor.ts +++ b/src/tools/monitor.ts @@ -1,10 +1,9 @@ #!/usr/bin/env node - -// This script is expected to run against a moonbeam blockchain import yargs from "yargs"; -import { getMonitoredApiFor, NETWORK_NAMES } from ".."; +import { getMonitoredApiFor, NETWORK_NAMES } from "../index.ts"; +// This script is expected to run against a moonbeam blockchain const argv = yargs(process.argv.slice(2)) .usage("Usage: $0") .version("1.0.0") diff --git a/src/tools/propose-motion.ts b/src/tools/propose-motion.ts index 131bf7d..fe6cd03 100644 --- a/src/tools/propose-motion.ts +++ b/src/tools/propose-motion.ts @@ -1,23 +1,22 @@ -/* - Performs a runtime upgrade through sudo or council (requires polkadot v0.9.32+) - -Ex: ./node_modules/.bin/ts-node-transpile-only src/tools/upgrade-network.ts \ - --url ws://localhost:9944 \ - --send-proposal-as council-external \ - --collective-threshold 3 \ - --proxy \ - --account-priv-key \ -*/ -import yargs from "yargs"; -import fs from "fs"; -import "@polkadot/api-augment"; +// Performs a runtime upgrade through sudo or council (requires polkadot v0.9.32+) +// +// Ex: bun src/tools/upgrade-network.ts \ +// --url ws://localhost:9944 \ +// --send-proposal-as council-external \ +// --collective-threshold 3 \ +// --proxy \ +// --account-priv-key import "@moonbeam-network/api-augment"; +import "@polkadot/api-augment"; + import { Keyring } from "@polkadot/api"; import { KeyringPair } from "@polkadot/keyring/types"; -import { getApiFor, NETWORK_YARGS_OPTIONS } from "../utils/networks"; -import { monitorSubmittedExtrinsic, waitForAllMonitoredExtrinsics } from "../utils/monitoring"; -import { maybeProxyCall } from "../utils/transactions"; -import { ALITH_PRIVATE_KEY } from "../utils/constants"; +import yargs from "yargs"; + +import { ALITH_PRIVATE_KEY } from "../utils/constants.ts"; +import { monitorSubmittedExtrinsic, waitForAllMonitoredExtrinsics } from "../utils/monitoring.ts"; +import { getApiFor, NETWORK_YARGS_OPTIONS } from "../utils/networks.ts"; +import { maybeProxyCall } from "../utils/transactions.ts"; const argv = yargs(process.argv.slice(2)) .usage("Usage: $0") diff --git a/src/tools/proxy-announcements.ts b/src/tools/proxy-announcements.ts index b0ab2a1..d278090 100644 --- a/src/tools/proxy-announcements.ts +++ b/src/tools/proxy-announcements.ts @@ -1,12 +1,11 @@ // This script is specific to the moonbeam foundation // It allows to verify proxy announcement against transfers from a csv file - import chalk from "chalk"; -import yargs from "yargs"; import fs from "fs"; import { table } from "table"; +import yargs from "yargs"; -import { getApiFor, NETWORK_YARGS_OPTIONS, numberWithCommas } from ".."; +import { getApiFor, NETWORK_YARGS_OPTIONS, numberWithCommas } from "../index.ts"; const argv = yargs(process.argv.slice(2)) .usage("Usage: $0") diff --git a/src/tools/relay-hrmp.ts b/src/tools/relay-hrmp.ts index 9e43106..86271c1 100644 --- a/src/tools/relay-hrmp.ts +++ b/src/tools/relay-hrmp.ts @@ -1,10 +1,11 @@ // This script is expected to run against a parachain network (using launch.ts script) -import yargs from "yargs"; +import "@polkadot/api-augment"; + import chalk from "chalk"; import { table } from "table"; -import "@polkadot/api-augment"; +import yargs from "yargs"; -import { getApiFor, NETWORK_YARGS_OPTIONS } from ".."; +import { getApiFor, NETWORK_YARGS_OPTIONS } from "../index.ts"; const argv = yargs(process.argv.slice(2)) .usage("Usage: $0") diff --git a/src/tools/run-moonbeam-fork.ts b/src/tools/run-moonbeam-fork.ts index 70d18ac..97b9620 100644 --- a/src/tools/run-moonbeam-fork.ts +++ b/src/tools/run-moonbeam-fork.ts @@ -1,23 +1,23 @@ // This script is expected to run against a parachain network (using launch.ts script) - -import moment from "moment"; -import prettyBytes from "pretty-bytes"; -import { SingleBar } from "cli-progress"; -import { runTask, spawnTask } from "../utils/runner"; -import { ChildProcessWithoutNullStreams } from "node:child_process"; -import semver from "semver"; -import yargs from "yargs"; import chalk from "chalk"; +import { SingleBar } from "cli-progress"; import fs from "fs/promises"; +import inquirer from "inquirer"; +import moment from "moment"; import fetch from "node-fetch"; +import { ChildProcessWithoutNullStreams } from "node:child_process"; import path from "path"; +import prettyBytes from "pretty-bytes"; +import semver from "semver"; +import yargs from "yargs"; + import { downloadExportedState, NetworkName, neutralizeExportedState, -} from "../libs/helpers/state-manipulator"; -import { ALITH_PRIVATE_KEY } from "../utils/constants"; -import inquirer from "inquirer"; +} from "../libs/helpers/state-manipulator/index.ts"; +import { ALITH_PRIVATE_KEY } from "../utils/constants.ts"; +import { runTask, spawnTask } from "../utils/runner.ts"; const argv = yargs(process.argv.slice(2)) .usage("Usage: $0") diff --git a/src/tools/scale.ts b/src/tools/scale.ts index 620751e..25b58d7 100755 --- a/src/tools/scale.ts +++ b/src/tools/scale.ts @@ -1,5 +1,4 @@ #!/usr/bin/env ts-node - /** * Usage: * @@ -24,13 +23,12 @@ * ... * }' */ - import { ApiPromise, WsProvider } from "@polkadot/api"; -import { u8aToHex, hexToU8a, hexToNumber, u8aToNumber, isArray } from "@polkadot/util"; -import { xxhashAsU8a, blake2AsU8a } from "@polkadot/util-crypto"; -import yargs from "yargs"; -import mergeWith from "lodash.mergewith"; +import { hexToU8a, isArray, u8aToHex } from "@polkadot/util"; +import { blake2AsU8a, xxhashAsU8a } from "@polkadot/util-crypto"; import isObject from "lodash.isobject"; +import mergeWith from "lodash.mergewith"; +import yargs from "yargs"; const args = yargs .showHelpOnFail(true) diff --git a/src/tools/search-account-transactions.ts b/src/tools/search-account-transactions.ts index 743440d..e8dc3c6 100644 --- a/src/tools/search-account-transactions.ts +++ b/src/tools/search-account-transactions.ts @@ -1,7 +1,6 @@ import yargs from "yargs"; -import fs from "fs"; -import { exploreBlockRange, getApiFor, NETWORK_YARGS_OPTIONS, reverseBlocks } from ".."; +import { getApiFor, NETWORK_YARGS_OPTIONS, reverseBlocks } from "../index.ts"; const argv = yargs(process.argv.slice(2)) .usage("Usage: $0") diff --git a/src/tools/send-clear-origin-messages-from-relay.ts b/src/tools/send-clear-origin-messages-from-relay.ts index 304f574..5ebc5bd 100644 --- a/src/tools/send-clear-origin-messages-from-relay.ts +++ b/src/tools/send-clear-origin-messages-from-relay.ts @@ -1,21 +1,10 @@ -import yargs from "yargs"; import "@polkadot/api-augment"; -import { Keyring } from "@polkadot/api"; -import { getApiFor } from "../utils/networks"; -import { getAccountIdentity } from "../utils/monitoring"; +import { Keyring } from "@polkadot/api"; import { BN } from "@polkadot/util"; -import { - isBigInt, - isBn, - isHex, - isNumber, - isU8a, - u8aConcat, - u8aToBn, - u8aToHex, - u8aToU8a, -} from "@polkadot/util"; +import yargs from "yargs"; + +import { getApiFor } from "../utils/networks.ts"; const argv = yargs(process.argv.slice(2)) .usage("Usage: $0") diff --git a/src/tools/storage-module-key.ts b/src/tools/storage-module-key.ts index 733e3b6..0ec62c5 100644 --- a/src/tools/storage-module-key.ts +++ b/src/tools/storage-module-key.ts @@ -1,5 +1,5 @@ -import { xxhashAsU8a } from "@polkadot/util-crypto"; import { u8aConcat, u8aToHex } from "@polkadot/util"; +import { xxhashAsU8a } from "@polkadot/util-crypto"; import yargs from "yargs"; const debug = require("debug")("main"); diff --git a/src/tools/upgrade-network.ts b/src/tools/upgrade-network.ts index 8229067..73c17ed 100644 --- a/src/tools/upgrade-network.ts +++ b/src/tools/upgrade-network.ts @@ -1,29 +1,28 @@ -/* - Performs a runtime upgrade through sudo or council (requires polkadot v0.9.32+) - -Ex: ./node_modules/.bin/ts-node-transpile-only src/tools/upgrade-network.ts \ - --url ws://localhost:9944 \ - --send-proposal-as council-external \ - --collective-threshold 3 \ - --proxy \ - --account-priv-key \ -*/ -import yargs from "yargs"; -import fs from "fs"; -import "@polkadot/api-augment"; +// Performs a runtime upgrade through sudo or council (requires polkadot v0.9.32+) +// +// Ex: bun src/tools/upgrade-network.ts \ +// --url ws://localhost:9944 \ +// --send-proposal-as council-external \ +// --collective-threshold 3 \ +// --proxy \ +// --account-priv-key import "@moonbeam-network/api-augment"; +import "@polkadot/api-augment"; + import { Keyring } from "@polkadot/api"; import { KeyringPair } from "@polkadot/keyring/types"; -import { getApiFor, NETWORK_YARGS_OPTIONS } from "../utils/networks"; import { blake2AsHex } from "@polkadot/util-crypto"; -import { hexToU8a } from "@polkadot/util"; +import fs from "fs"; +import yargs from "yargs"; + +import { ALITH_PRIVATE_KEY } from "../utils/constants.ts"; import { monitorSubmittedExtrinsic, waitBlocks, waitForAllMonitoredExtrinsics, -} from "../utils/monitoring"; -import { maybeProxyCall } from "../utils/transactions"; -import { ALITH_PRIVATE_KEY } from "../utils/constants"; +} from "../utils/monitoring.ts"; +import { getApiFor, NETWORK_YARGS_OPTIONS } from "../utils/networks.ts"; +import { maybeProxyCall } from "../utils/transactions.ts"; const argv = yargs(process.argv.slice(2)) .usage("Usage: $0") @@ -51,6 +50,11 @@ const argv = yargs(process.argv.slice(2)) demandOption: false, conflicts: ["send-proposal-as", "collective-threshold"], }, + enact: { + type: "boolean", + demandOption: false, + conflicts: ["sudo", "send-proposal-as", "collective-threshold"], + }, alith: { type: "boolean", demandOption: false, @@ -90,7 +94,7 @@ async function main() { const collectiveThreshold = argv["collective-threshold"] || Math.ceil(((await api.query.openTechCommitteeCollective.members()).length * 3) / 5); - const proposalAmount = api.consts.democracy.minimumDeposit; + const proposalAmount = api.consts?.democracy?.minimumDeposit || 0n; let account: KeyringPair; let nonce; @@ -110,13 +114,19 @@ async function main() { console.log(`Unexpected runtime ${codeHash} size: ${code.length}`); process.exit(1); } - console.log(`Using runtime wasm with size: ${code.length}`); + console.log(`Using runtime wasm with size: ${code.length} [hash: ${codeHash}]`); const tryProxy = (call) => { return maybeProxyCall(api, call, argv["proxy"], argv["proxy-type"]); }; - if (argv["sudo"]) { + if (argv["enact"]) { + await tryProxy(api.tx.system.applyAuthorizedUpgrade(codeHex)).signAndSend( + account, + { nonce: nonce++ }, + monitorSubmittedExtrinsic(api, { id: "sudo" }), + ); + } else if (argv["sudo"]) { const proposal = api.tx.system.setCode(codeHex); await tryProxy(api.tx.sudo.sudo(proposal)).signAndSend( account, diff --git a/src/tools/vote-motion.ts b/src/tools/vote-motion.ts index 131bf7d..f0e3597 100644 --- a/src/tools/vote-motion.ts +++ b/src/tools/vote-motion.ts @@ -1,23 +1,22 @@ -/* - Performs a runtime upgrade through sudo or council (requires polkadot v0.9.32+) - -Ex: ./node_modules/.bin/ts-node-transpile-only src/tools/upgrade-network.ts \ - --url ws://localhost:9944 \ - --send-proposal-as council-external \ - --collective-threshold 3 \ - --proxy \ - --account-priv-key \ -*/ -import yargs from "yargs"; -import fs from "fs"; -import "@polkadot/api-augment"; +// Performs a runtime upgrade through sudo or council (requires polkadot v0.9.32+) +// +// Ex: bun src/tools/upgrade-network.ts \ +// --url ws://localhost:9944 \ +// --send-proposal-as council-external \ +// --collective-threshold 3 \ +// --proxy \ +// --account-priv-key \ import "@moonbeam-network/api-augment"; +import "@polkadot/api-augment"; + import { Keyring } from "@polkadot/api"; import { KeyringPair } from "@polkadot/keyring/types"; -import { getApiFor, NETWORK_YARGS_OPTIONS } from "../utils/networks"; -import { monitorSubmittedExtrinsic, waitForAllMonitoredExtrinsics } from "../utils/monitoring"; -import { maybeProxyCall } from "../utils/transactions"; -import { ALITH_PRIVATE_KEY } from "../utils/constants"; +import yargs from "yargs"; + +import { ALITH_PRIVATE_KEY } from "../utils/constants.ts"; +import { monitorSubmittedExtrinsic, waitForAllMonitoredExtrinsics } from "../utils/monitoring.ts"; +import { getApiFor, NETWORK_YARGS_OPTIONS } from "../utils/networks.ts"; +import { maybeProxyCall } from "../utils/transactions.ts"; const argv = yargs(process.argv.slice(2)) .usage("Usage: $0") diff --git a/src/utils/monitoring.ts b/src/utils/monitoring.ts index 5d2990e..5337b96 100644 --- a/src/utils/monitoring.ts +++ b/src/utils/monitoring.ts @@ -1,23 +1,23 @@ -import type { ApiPromise } from "@polkadot/api"; -import type { Extrinsic, BlockHash, EventRecord } from "@polkadot/types/interfaces"; -import type { Block } from "@polkadot/types/interfaces/runtime/types"; -import { Data, GenericEthereumAccountId, Option, u128, u8, bool } from "@polkadot/types"; +import "@moonbeam-network/api-augment"; +import "@polkadot/api-augment"; + +import { ApiDecoration } from "@polkadot/api/types"; +import { Data, GenericEthereumAccountId, Option, u128 } from "@polkadot/types"; +import { Codec, ITuple } from "@polkadot/types-codec/types"; +import { PalletIdentityRegistration } from "@polkadot/types/lookup"; import { ISubmittableResult } from "@polkadot/types/types"; -import type { EthereumTransactionTransactionV2 } from "@polkadot/types/lookup"; -import type { LegacyTransaction } from "@polkadot/types/interfaces/eth"; import { u8aToString } from "@polkadot/util"; import { ethereumEncode } from "@polkadot/util-crypto"; -import { mapExtrinsics, TxWithEventAndFee } from "./types"; - -import "@polkadot/api-augment"; -import "@moonbeam-network/api-augment"; - import chalk from "chalk"; import Debug from "debug"; -import { PalletIdentityRegistration } from "@polkadot/types/lookup"; -import { Codec, ITuple } from "@polkadot/types-codec/types"; -import { promiseConcurrent } from "./functions"; -import { ApiDecoration } from "@polkadot/api/types"; + +import { promiseConcurrent } from "./functions.ts"; +import { mapExtrinsics, TxWithEventAndFee } from "./types.ts"; + +import type { ApiPromise } from "@polkadot/api"; +import type { Extrinsic, BlockHash, EventRecord } from "@polkadot/types/interfaces"; +import type { Block } from "@polkadot/types/interfaces/runtime/types"; +import type { LegacyTransaction } from "@polkadot/types/interfaces/eth"; const debug = Debug("monitoring"); export const printTokens = (api: ApiPromise, tokens: bigint, decimals = 2, pad = 9) => { @@ -37,6 +37,7 @@ export interface BlockDetails { records: EventRecord[]; txWithEvents: TxWithEventAndFee[]; weightPercentage: number; + storageUsed: number; } // TODO: Improve with cache and eviction @@ -108,11 +109,11 @@ export const getAccountIdentities = async ( const superIdentityOpts = validSuperOfs.length > 0 ? await api.rpc.state.queryStorageAt[]>( - validSuperOfs.map( - (superOf) => api.query.identity.identityOf.key(superOf[0].toString()), - at, - ), - ) + validSuperOfs.map( + (superOf) => api.query.identity.identityOf.key(superOf[0].toString()), + at, + ), + ) : []; let index = 0; return superOfs.map((superOf) => { @@ -147,9 +148,8 @@ export const getAccountIdentities = async ( return account && identity ? u8aToString(identity.info.display.asRaw.toU8a(true)) : superOf && superOf.identity - ? `${u8aToString(superOf.identity.info.display.asRaw.toU8a(true))} - Sub ${ - (superOf.data && u8aToString(superOf.data.asRaw.toU8a(true))) || "" - }` + ? `${u8aToString(superOf.identity.info.display.asRaw.toU8a(true))} - Sub ${(superOf.data && u8aToString(superOf.data.asRaw.toU8a(true))) || "" + }` : account?.toString(); }); }; @@ -164,26 +164,26 @@ export const getAccountIdentity = async ( if (!identityCache[account] || identityCache[account].lastUpdate < Date.now() - 3600 * 1000) { const [identity, superOfIdentity] = api.query.identity ? await Promise.all([ - api.query.identity - .identityOf(account.toString()) - .then((a) => (a.isSome ? a.unwrap() : null)), - api.query.identity.superOf(account.toString()).then(async (superOfOpt) => { - const superOf = (superOfOpt.isSome && superOfOpt.unwrap()) || null; - if (!superOf) { - return null; - } - const identityOpt = await api.query.identity.identityOf(superOf[0].toString()); - const identity = (identityOpt.isSome && identityOpt.unwrap()) || null; - return { - identity, - data: superOf[1], - }; - }), - ]) + api.query.identity + .identityOf(account.toString()) + .then((a) => (a.isSome ? a.unwrap() : null)), + api.query.identity.superOf(account.toString()).then(async (superOfOpt) => { + const superOf = (superOfOpt.isSome && superOfOpt.unwrap()) || null; + if (!superOf) { + return null; + } + const identityOpt = await api.query.identity.identityOf(superOf[0].toString()); + const identity = (identityOpt.isSome && identityOpt.unwrap()) || null; + return { + identity, + data: superOf[1], + }; + }), + ]) : [null, null]; identityCache[account] = { lastUpdate: Date.now(), - identity: identity[0], + identity: identity?.[0], superOf: superOfIdentity && ("info" in superOfIdentity ? superOfIdentity : superOfIdentity[0]), }; @@ -193,9 +193,8 @@ export const getAccountIdentity = async ( return identity ? u8aToString(identity.info.display.asRaw.toU8a(true)) : superOf - ? `${u8aToString(superOf.identity.info.display.asRaw.toU8a(true))} - Sub ${ - (superOf.data && u8aToString(superOf.data.asRaw.toU8a(true))) || "" - }` + ? `${u8aToString(superOf.identity.info.display.asRaw.toU8a(true))} - Sub ${(superOf.data && u8aToString(superOf.data.asRaw.toU8a(true))) || "" + }` : account?.toString(); }; @@ -203,6 +202,9 @@ export const getAccountFromNimbusKey = async ( api: ApiPromise | ApiDecoration<"promise">, nmbsKey: string, ): Promise => { + if (!nmbsKey) { + return null + } if ( !authorMappingCache[nmbsKey] || authorMappingCache[nmbsKey].lastUpdate < Date.now() - 3600 * 1000 @@ -295,26 +297,46 @@ export const getBlockDetails = async (api: ApiPromise, blockHash: BlockHash) => fees.map((fee) => fee.inclusionFee.unwrapOrDefault()), feeMultiplier, ); - const blockWeight = txWithEvents.reduce((totalWeight, tx, index) => { - // TODO: support weight v1/2 - if (!tx.dispatchInfo) { - return totalWeight; - } - const refTime = (tx.dispatchInfo.weight as any).toBn - ? (tx.dispatchInfo.weight as any).toBigInt() - : tx.dispatchInfo.weight.refTime?.toBigInt(); - return totalWeight + refTime; - }, 0n); + + const [blockWeight, ethWeight] = txWithEvents.reduce( + (stats, tx, index) => { + // TODO: support weight v1/2 + if (!tx.dispatchInfo) { + return stats; + } + const refTime = (tx.dispatchInfo.weight as any).toBn + ? (tx.dispatchInfo.weight as any).toBigInt() + : tx.dispatchInfo.weight.refTime?.toBigInt(); + return [ + stats[0] + refTime, + stats[1] + (tx.extrinsic.method.section == "ethereum" ? refTime : 0n), + ]; + }, + [0n, 0n], + ); + + const gasUsed = (await api.rpc.eth.getBlockByNumber(block.header.number.toNumber(), false)) + .unwrap() + .gasUsed.toBigInt(); + + const WEIGHT_TO_GAS_RATIO = 25_000n; // TODO: Find a way to retrieve dynamically + const GAS_LIMIT_STORAGE_GROWTH_RATIO = 366n; // TODO: Find a way to retrieve dynamically + const gasByRefTime = ethWeight / WEIGHT_TO_GAS_RATIO; + // console.log(`[${block.header.number.toNumber()} ${blockWeight}/${ethWeight}: ${gasByRefTime}/${gasUsed}`); + const storageUsed = + gasByRefTime != gasUsed ? Number(gasByRefTime / GAS_LIMIT_STORAGE_GROWTH_RATIO) : 0; // in bytes + return { block, isAuthorOrbiter: - collatorId.unwrapOr(null)?.toString() != + (collatorId as any).unwrapOr(null)?.toString() != (await getAccountFromNimbusKey(apiAt, nmbsKey))?.toString(), authorName, blockTime: blockTime.toNumber(), weightPercentage: Number((blockWeight * 10000n) / maxBlockWeight) / 100, txWithEvents, records, + storageUsed, } as BlockDetails; }; @@ -454,6 +476,16 @@ export function generateBlockDetailsLog( ? chalk.green(weight) : weight; + const storage = blockDetails.storageUsed.toFixed(0).padStart(5, " "); + const storageText = + blockDetails.storageUsed > 100000 + ? chalk.red(storage) + : blockDetails.storageUsed > 10000 + ? chalk.yellow(storage) + : blockDetails.storageUsed > 1000 + ? chalk.green(storage) + : storage; + let txPoolText = null; let poolIncText = null; if ("pendingTxs" in blockDetails) { @@ -503,14 +535,14 @@ export function generateBlockDetailsLog( .filter(({ dispatchInfo }) => !dispatchInfo.class.isMandatory) .reduce((p, { dispatchInfo, extrinsic, events, fees }) => { if (extrinsic.method.section == "ethereum") { - const payload = extrinsic.method.args[0] as EthereumTransactionTransactionV2; + const payload = extrinsic.method.args[0] as any; let gasPrice = payload.isLegacy ? payload.asLegacy?.gasPrice.toBigInt() : payload.isEip2930 ? payload.asEip2930?.gasPrice.toBigInt() : payload.isEip1559 ? // If gasPrice is not indicated, we should use the base fee defined in that block - payload.asEip1559?.maxFeePerGas.toBigInt() || 0n + payload.asEip1559?.maxFeePerGas.toBigInt() || 0n : (payload as any as LegacyTransaction).gasPrice?.toBigInt(); const refTime = (dispatchInfo.weight as any).toBn @@ -534,14 +566,14 @@ export function generateBlockDetailsLog( const transferred = blockDetails.txWithEvents .map((tx) => { if (tx.extrinsic.method.section == "ethereum" && tx.extrinsic.method.method == "transact") { - const payload = tx.extrinsic.method.args[0] as EthereumTransactionTransactionV2; + const payload = tx.extrinsic.method.args[0] as any; let gasPrice = payload.isLegacy ? payload.asLegacy?.gasPrice.toBigInt() : payload.isEip2930 ? payload.asEip2930?.gasPrice.toBigInt() : payload.isEip1559 ? // If gasPrice is not indicated, we should use the base fee defined in that block - payload.asEip1559?.maxFeePerGas.toBigInt() || 0n + payload.asEip1559?.maxFeePerGas.toBigInt() || 0n : (payload as any as LegacyTransaction).gasPrice?.toBigInt(); } return tx.events.reduce((total, event) => { @@ -566,8 +598,8 @@ export function generateBlockDetailsLog( const authorId = blockDetails.authorName.length > 24 ? `${blockDetails.authorName.substring(0, 9)}..${blockDetails.authorName.substring( - blockDetails.authorName.length - 6, - )}` + blockDetails.authorName.length - 6, + )}` : blockDetails.authorName; const authorName = blockDetails.isAuthorOrbiter ? chalk.yellow(authorId) : authorId; @@ -583,11 +615,10 @@ export function generateBlockDetailsLog( .padEnd( 7, " ", - )} [${weightText}%, ${feesText} fees, ${extText} Txs (${evmText} Eth)(<->${coloredTransferred})]${ - txPoolText ? `[Pool:${txPoolText}${poolIncText ? `(+${poolIncText})` : ""}]` : `` - }${secondText ? `[${secondText}s]` : ""}(hash: ${hash.substring(0, 7)}..${hash.substring( - hash.length - 4, - )})${options?.suffix ? ` ${options.suffix}` : ""} by ${authorName}`; + )} [${weightText}%, ${storageText}B, ${feesText} fees, ${extText} Txs (${evmText} Eth)(<->${coloredTransferred})]${txPoolText ? `[Pool:${txPoolText}${poolIncText ? `(+${poolIncText})` : ""}]` : `` + }${secondText ? `[${secondText}s]` : ""}(hash: ${hash.substring(0, 7)}..${hash.substring( + hash.length - 4, + )})${options?.suffix ? ` ${options.suffix}` : ""} by ${authorName}`; } export function printBlockDetails( diff --git a/src/utils/networks.ts b/src/utils/networks.ts index ddfdc88..12b1876 100644 --- a/src/utils/networks.ts +++ b/src/utils/networks.ts @@ -1,21 +1,19 @@ -import { WsProvider, HttpProvider } from "@polkadot/api"; +import { ApiPromise, HttpProvider, WsProvider } from "@polkadot/api"; import chalk from "chalk"; +import { typesBundlePre900 } from "moonbeam-types-bundle"; import { Chain, + createPublicClient, + createWalletClient, PrivateKeyAccount, PublicClient, Transport, WalletClient, - createPublicClient, - createWalletClient, webSocket, } from "viem"; -import { ApiPromise } from "@polkadot/api"; -import { typesBundlePre900 } from "moonbeam-types-bundle"; -import { listenBlocks, printBlockDetails, RealtimeBlockDetails } from "./monitoring"; import { Options } from "yargs"; -import { privateKeyToAccount } from "viem/accounts"; -import { localhost } from "viem/chains"; + +import { listenBlocks, printBlockDetails, RealtimeBlockDetails } from "./monitoring.ts"; export type MOONBEAM_NETWORK_NAME = | "stagenet" @@ -127,7 +125,7 @@ export const getWsProviderFor = (argv: Argv) => { if (isKnownNetwork(argv.network)) { return getWsProviderForNetwork(argv.network); } - return new WsProvider(argv.url); + return new WsProvider(argv.url || process.env.MOONBEAM_TOOLS_WS_URL); }; export const getHttpProviderForNetwork = (name: NETWORK_NAME) => { diff --git a/src/utils/proxy-chain.ts b/src/utils/proxy-chain.ts index d6ee080..e123a4e 100644 --- a/src/utils/proxy-chain.ts +++ b/src/utils/proxy-chain.ts @@ -1,10 +1,11 @@ -import Debug from "debug"; import { ApiPromise, Keyring } from "@polkadot/api"; import { SubmittableExtrinsic } from "@polkadot/api/types"; -import { ISubmittableResult, Callback } from "@polkadot/types/types"; -import type { MoonbeamRuntimeProxyType } from "@polkadot/types/lookup"; import { KeyringPair } from "@polkadot/keyring/types"; +import { Callback, ISubmittableResult } from "@polkadot/types/types"; +import Debug from "debug"; import { Options } from "yargs"; + +import type { MoonbeamRuntimeProxyType } from "@polkadot/types/lookup"; const debug = Debug("proxy-chain"); // Library providing easy support for multiple proxy signing for CLI diff --git a/src/utils/referenda.ts b/src/utils/referenda.ts index fc12dbf..4f019f9 100644 --- a/src/utils/referenda.ts +++ b/src/utils/referenda.ts @@ -1,16 +1,14 @@ // Extracted from useReference from @polkadot/apps page-referenda - import { ApiPromise } from "@polkadot/api"; import { DeriveProposalImage } from "@polkadot/api-derive/types"; import { ApiDecoration } from "@polkadot/api/types"; -import type { Bytes } from "@polkadot/types"; -import type { Call, Hash } from "@polkadot/types/interfaces"; import { FrameSupportPreimagesBounded, PalletConvictionVotingTally, PalletPreimageRequestStatus, PalletRankedCollectiveTally, PalletReferendaCurve, + PalletReferendaReferendumInfo, PalletReferendaReferendumInfoConvictionVotingTally, PalletReferendaReferendumInfoRankedCollectiveTally, PalletReferendaReferendumStatusConvictionVotingTally, @@ -18,26 +16,29 @@ import { } from "@polkadot/types/lookup"; import { BN, - bnMax, - bnMin, BN_BILLION, BN_ONE, BN_ZERO, + bnMax, + bnMin, isString, stringPascalCase, } from "@polkadot/util"; import { HexString } from "@polkadot/util/types"; import Debug from "debug"; -import { promiseConcurrent } from "./functions"; + +import { promiseConcurrent } from "./functions.ts"; + +import type { Bytes } from "@polkadot/types"; +import type { Call, Hash } from "@polkadot/types/interfaces"; +import { PalletReferendaReferendumStatus } from "@polkadot/types/lookup"; const debug = Debug("tools:referenda"); export interface Referendum { decidingEnd?: BN; id: number; - ongoing: PalletReferendaReferendumStatusConvictionVotingTally; - info: - | PalletReferendaReferendumInfoConvictionVotingTally - | PalletReferendaReferendumInfoRankedCollectiveTally; + ongoing: PalletReferendaReferendumStatus; + info: PalletReferendaReferendumInfo; isConvictionVote: boolean; key: string; track?: PalletReferendaTrackInfo; @@ -148,10 +149,8 @@ function calcDecidingEnd( } export function isConvictionVote( - info: - | PalletReferendaReferendumInfoConvictionVotingTally - | PalletReferendaReferendumInfoRankedCollectiveTally, -): info is PalletReferendaReferendumInfoConvictionVotingTally { + info: PalletReferendaReferendumInfo, +): info is PalletReferendaReferendumInfo { return info.isOngoing && isConvictionTally(info.asOngoing.tally); } @@ -220,9 +219,7 @@ async function getImageProposal(api: ApiPromise | ApiDecoration<"promise">, hash } // Returns the block at which the referendum ended, 0 if onGoing; -function getReferendumConclusionBlock( - info: PalletReferendaReferendumInfoConvictionVotingTally, -): number { +function getReferendumConclusionBlock(info: PalletReferendaReferendumInfo): number { if (info.isOngoing) { return 0; } @@ -245,7 +242,7 @@ function getReferendumConclusionBlock( async function getReferendumOnGoing( api: ApiPromise, id: number, - info: PalletReferendaReferendumInfoConvictionVotingTally, + info: PalletReferendaReferendumInfo, ) { if (info.isOngoing) { return { apiAt: api, ongoing: info.asOngoing }; diff --git a/src/utils/runner.ts b/src/utils/runner.ts index 11dc0aa..f592061 100644 --- a/src/utils/runner.ts +++ b/src/utils/runner.ts @@ -1,7 +1,8 @@ import child_process from "child_process"; -import { promisify } from "node:util"; import Debug from "debug"; import { ChildProcessWithoutNullStreams } from "node:child_process"; +import { promisify } from "node:util"; + const debug = Debug("actions:runner"); const execAsync = promisify(child_process.exec); diff --git a/src/utils/storage.ts b/src/utils/storage.ts index 4a588ae..54e4c5a 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -1,6 +1,6 @@ -import type { ProviderInterface } from "@polkadot/rpc-provider/types"; -import { promiseConcurrent } from "./functions"; +import { promiseConcurrent } from "./functions.ts"; +import type { ProviderInterface } from "@polkadot/rpc-provider/types"; const debug = require("debug")("utils:storage-query"); // Timer must be wrapped to be passed diff --git a/src/utils/transactions.ts b/src/utils/transactions.ts index a5f7aaa..cdf45d5 100644 --- a/src/utils/transactions.ts +++ b/src/utils/transactions.ts @@ -1,8 +1,6 @@ import { ApiPromise } from "@polkadot/api"; import { SubmittableExtrinsic } from "@polkadot/api/promise/types"; import { GenericCall } from "@polkadot/types/generic"; -import { PalletPreimageRequestStatus } from "@polkadot/types/lookup"; -import { Option } from "@polkadot/types"; export const sendAllAndWaitLast = async (extrinsics: SubmittableExtrinsic[]) => { return new Promise(async (resolve, reject) => { diff --git a/src/utils/types.ts b/src/utils/types.ts index 64eb407..4b6eaa2 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,3 +1,7 @@ +import { ApiPromise } from "@polkadot/api"; + +import { EXTRINSIC_BASE_WEIGHT } from "./constants.ts"; + import type { DispatchError, DispatchInfo, @@ -6,13 +10,7 @@ import type { InclusionFee, } from "@polkadot/types/interfaces"; import type { u128 } from "@polkadot/types"; -import { BN } from "@polkadot/util"; - import type { TxWithEvent } from "@polkadot/api-derive/types"; -import { ApiPromise } from "@polkadot/api"; -import { toPairsIn } from "lodash"; -import { EXTRINSIC_BASE_WEIGHT } from "./constants"; - export interface ComputedFees { baseFee: bigint; lenFee: bigint; diff --git a/src/utils/web3/contracts.ts b/src/utils/web3/contracts.ts index 62169bb..0517801 100644 --- a/src/utils/web3/contracts.ts +++ b/src/utils/web3/contracts.ts @@ -1,7 +1,7 @@ -import Web3 from "web3"; import * as rlp from "rlp"; -import { customWeb3Request } from "./transactions"; -import { Account, TransactionReceipt } from "web3-core"; +import { TransactionReceipt, Web3, Web3BaseWalletAccount } from "web3"; + +import { customWeb3Request } from "./transactions.ts"; export interface SolidityContractBundle { abi: any; @@ -15,7 +15,7 @@ export interface SolidityContractBundle { export const deployContract = async ( web3: Web3, contract: SolidityContractBundle, - deployer: Account, + deployer: Web3BaseWalletAccount, nonce: number, gasLimit = 1000000, ) => { @@ -27,7 +27,7 @@ export const deployContract = async ( const code = await customWeb3Request(web3, "eth_getCode", [contractAddress]); if (code && code.result && code.result != "0x") { - console.log(`Contract already deployed: ${code.result.length} bytes`); + console.log(`Contract already deployed: ${code.result?.["length"]} bytes`); return; } @@ -68,7 +68,7 @@ export const callContract = async ( contractBundle: SolidityContractBundle, contractAddress: string, call: { funcName: string; params: any[]; gasLimit: number }, - caller: Account, + caller: Web3BaseWalletAccount, nonce: number, ) => { const contract = new web3.eth.Contract(contractBundle.abi, contractAddress); diff --git a/src/utils/web3/solidity.ts b/src/utils/web3/solidity.ts index eb6b69c..8b0e139 100644 --- a/src/utils/web3/solidity.ts +++ b/src/utils/web3/solidity.ts @@ -1,5 +1,6 @@ import solc from "solc"; -import { SolidityContractBundle } from "./contracts"; + +import { SolidityContractBundle } from "./contracts.ts"; export function compileSolidity( contractContent: string, diff --git a/src/utils/web3/transactions.ts b/src/utils/web3/transactions.ts index 4fd859e..73b71fa 100644 --- a/src/utils/web3/transactions.ts +++ b/src/utils/web3/transactions.ts @@ -1,9 +1,8 @@ -import { JsonRpcResponse } from "web3-core-helpers"; -import Web3 from "web3"; +import { JsonRpcResponse, JsonRpcResponseWithResult, Web3 } from "web3"; let globalId = 10000; export async function customWeb3Request(web3: Web3, method: string, params: any[]) { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const id = globalId++; (web3.currentProvider as any).send( { @@ -21,7 +20,7 @@ export async function customWeb3Request(web3: Web3, method: string, params: any[ }`, ); } - resolve(result); + resolve(result as JsonRpcResponseWithResult); }, ); }); diff --git a/test/sample-state.json b/test/sample-state.json index 5f41cef..cfae335 100644 --- a/test/sample-state.json +++ b/test/sample-state.json @@ -116,6 +116,7 @@ "0x45323df7cc47150b3930e2666b0aa313d422e17d2affdce4a912d187a734dd67": "0x09068fb148029ac3482b5096b7ab8a196720f445474b612378ed272d9e1da91830b73e86ab00e92b950bf6be4eb982268d0a9168f5b42c7d5f025f33f20ecf5c5b5941272f9ff507348b0b431154f273fb96eec37dd294b66b366a19a9f5f738dd3745be589c10066e6d627380f2548664997fc77e7a04adeb0f60263fe67877f8a41084565ead7f555f3a25490672616e648101b2d83cbbe336e457b369bfc3aa53882fa39539e526fa52dadd87f990846bf1147b5990ccfeb79231b7fdd510c85365cdf699a4e975250978111aca28c960680c6016ff503e7916cf2808242d57d6d3d0725a1f0fae3b1e9b0f59a32ae9ef1b040466726f6e090101a8b8a6f7ac20215d00a5dfc3c63c0553bf240706a120e9e3a04c9482692a41b304f5f08c9b881f0dbcfccfaaf409d5b978522cc139e456b2f8be8c2ab0da3e473d056e6d62730101288cb5034e627fd3b6eb0e43cfb643cd1252f801019d169ba6379bb9067de51aa79ce5b8055c2fcb80e904ba2716e49f4be16b00e6e51041b449d7705f130b8f601461003134427780f1b1ddf031572862912382837f80d931bd43292906196596c59f2e00005000", "0x47c9410b11325752265d54845357656f4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", "0x4ae7e256f92e5888372d72f3e4db10034e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x51bbb05ff9b574192142ecc49ac965d7ba7fb8745735dc3be2a2c61a72c39e78": "0x08f24ff3a9cf04c71dbc94d0b566f7a27b94566cac3cd0a705a2dc65e5b1e1205896baa2be8a07c6e0", "0x54f9db3490626a75fb6ecd4b909679f04e7b9012096b41c4eb3aaf947f6ea429": "0x0000", "0x5b372fc04a0451c794728fe29e4026694e7b9012096b41c4eb3aaf947f6ea429": "0x0000", "0x5b372fc04a0451c794728fe29e402669e9e0ec07005839bd9935e1fc3cd7a7904f9aea1afa791265fae359272badc1cf8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0x3cd0a705a2dc65e5b1e1205896baa2be8a07c6e0000010632d5ec76b05000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", @@ -138,7 +139,6 @@ "0x89d139e01a5eb2256f222e5fc5dbe6b34e7b9012096b41c4eb3aaf947f6ea429": "0x0000", "0x9098511cb91f6969fb7b22636ce7f3984e7b9012096b41c4eb3aaf947f6ea429": "0x0000", "0xa06bfb73a86f8f98d5c5dc14e20e8a034e7b9012096b41c4eb3aaf947f6ea429": "0x0400", - "0xa06bfb73a86f8f98d5c5dc14e20e8a03ba7fb8745735dc3be2a2c61a72c39e78": "0x08f24ff3a9cf04c71dbc94d0b566f7a27b94566cac3cd0a705a2dc65e5b1e1205896baa2be8a07c6e0", "0xa0eb495036d368196a2b6c51d9d788814e7b9012096b41c4eb3aaf947f6ea429": "0x0000", "0xa686a3043d0adcf2fa655e57bc595a7813792e785168f725b60e2969c7fc2552": "0x010000000000000058020000", "0xa686a3043d0adcf2fa655e57bc595a781d7912799a6cf05f0394865bd5bb61bd": "0x00000000000000000000000000000000000000001e", diff --git a/test/state-manipulation.spec.ts b/test/state-manipulation.spec.ts index 7ad99d0..6e14b7b 100644 --- a/test/state-manipulation.spec.ts +++ b/test/state-manipulation.spec.ts @@ -1,25 +1,27 @@ -import path from "node:path"; +import { hexToBigInt, nToHex } from "@polkadot/util"; import fs from "node:fs/promises"; -import { processState } from "../src/libs/helpers/state-manipulator/genesis-parser"; -import { RoundManipulator } from "../src/libs/helpers/state-manipulator/round-manipulator"; -import { SudoManipulator } from "../src/libs/helpers/state-manipulator/sudo-manipulator"; -import { AuthorFilteringManipulator } from "../src/libs/helpers/state-manipulator/author-filtering-manipulator"; -import { BalancesManipulator } from "../src/libs/helpers/state-manipulator/balances-manipulator"; -import { CollatorManipulator } from "../src/libs/helpers/state-manipulator/collator-manipulator"; -import { HRMPManipulator } from "../src/libs/helpers/state-manipulator/hrmp-manipulator"; -import { SpecManipulator } from "../src/libs/helpers/state-manipulator/spec-manipulator"; -import { XCMPManipulator } from "../src/libs/helpers/state-manipulator/xcmp-manipulator"; -import { CollectiveManipulator } from "../src/libs/helpers/state-manipulator/collective-manipulator"; -import { ValidationManipulator } from "../src/libs/helpers/state-manipulator/validation-manipulator"; +import path from "node:path"; + +import { AuthorFilteringManipulator } from "../src/libs/helpers/state-manipulator/author-filtering-manipulator.ts"; +import { AuthorizeUpgradeManipulator } from "../src/libs/helpers/state-manipulator/authorize-upgrade-manipulator.ts"; +import { BalancesManipulator } from "../src/libs/helpers/state-manipulator/balances-manipulator.ts"; +import { CollatorManipulator } from "../src/libs/helpers/state-manipulator/collator-manipulator.ts"; +import { CollectiveManipulator } from "../src/libs/helpers/state-manipulator/collective-manipulator.ts"; +import { processState } from "../src/libs/helpers/state-manipulator/genesis-parser.ts"; +import { HRMPManipulator } from "../src/libs/helpers/state-manipulator/hrmp-manipulator.ts"; +import { RoundManipulator } from "../src/libs/helpers/state-manipulator/round-manipulator.ts"; +import { SpecManipulator } from "../src/libs/helpers/state-manipulator/spec-manipulator.ts"; +import { SudoManipulator } from "../src/libs/helpers/state-manipulator/sudo-manipulator.ts"; +import { ValidationManipulator } from "../src/libs/helpers/state-manipulator/validation-manipulator.ts"; +import { XCMPManipulator } from "../src/libs/helpers/state-manipulator/xcmp-manipulator.ts"; import { + BALTATHAR_ADDRESS, CHARLETH_ADDRESS, CHARLETH_SESSION_ADDRESS, HEATH_ADDRESS, JUDITH_ADDRESS, - BALTATHAR_ADDRESS, -} from "../src/utils/constants"; -import { hexToBigInt, nToHex } from "@polkadot/util"; -import { AuthorizeUpgradeManipulator } from "../src/libs/helpers/state-manipulator/authorize-upgrade-manipulator"; +} from "../src/utils/constants.ts"; +import { CumulusManipulator } from "src/libs/helpers/state-manipulator/cumulus-manipulator.ts"; describe("State Manipulation", () => { const inFile = path.join(__dirname, "sample-state.json"); @@ -34,6 +36,7 @@ describe("State Manipulation", () => { }), new SudoManipulator(JUDITH_ADDRESS), new AuthorFilteringManipulator(100), + new CumulusManipulator(288730710n), new AuthorizeUpgradeManipulator( "0xfb9f16ba6b3433ba2a273974207260c7ace6aa629992d492bad0ba873b39762d", ), @@ -122,7 +125,8 @@ describe("State Manipulation", () => { ).toBeUndefined(); }); - it("Should replace the council members with JUDITH_ADDRESS", async () => { + // Council has been removed + it.skip("Should replace the council members with JUDITH_ADDRESS", async () => { expect( finalState["0xd59b9be6f0a7187ca6630c1d0a9bb045ba7fb8745735dc3be2a2c61a72c39e78"], ).toEqual(`0x04${JUDITH_ADDRESS.slice(2)}`); @@ -130,7 +134,7 @@ describe("State Manipulation", () => { it("Should replace the technical committee members with Charleth and Heath", async () => { expect( - finalState["0xa06bfb73a86f8f98d5c5dc14e20e8a03ba7fb8745735dc3be2a2c61a72c39e78"], + finalState["0x51bbb05ff9b574192142ecc49ac965d7ba7fb8745735dc3be2a2c61a72c39e78"], ).toEqual(`0x08${CHARLETH_ADDRESS.slice(2)}${HEATH_ADDRESS.slice(2)}`); }); @@ -148,8 +152,8 @@ describe("State Manipulation", () => { it("Should contain authorized upgrade hash", async () => { expect( - finalState["0x45323df7cc47150b3930e2666b0aa3132fa9f1bf25567808771bff091dc89ecd"], - ).toEqual("0xfb9f16ba6b3433ba2a273974207260c7ace6aa629992d492bad0ba873b39762d"); + finalState["0x26aa394eea5630e07c48ae0c9558cef72fa9f1bf25567808771bff091dc89ecd"], + ).toEqual("0xfb9f16ba6b3433ba2a273974207260c7ace6aa629992d492bad0ba873b39762d01"); }); it("Should set Charleth, Heath and Judith to 1000 tokens", async () => { @@ -214,4 +218,10 @@ describe("State Manipulation", () => { finalState["0x5c0d1176a568c1f92944340dbfed9e9c530ebca703c85910e7164cb7d1c9e47b"], ).toEqual(JUDITH_ADDRESS); }); + + it("Should have the slot info reset to 0", async () => { + expect( + finalState["0x8985dff79e6002d0deba9ddac46f32a5a70806914c906d747e668a21f9021729"], + ).toEqual("0x56ae35110000000001000000"); + }); }); diff --git a/tsconfig.json b/tsconfig.json index 856e39e..7287d5b 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,15 @@ { "compilerOptions": { "target": "esnext", - "module": "CommonJS", - "moduleResolution": "node", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "allowSyntheticDefaultImports": true, + "allowImportingTsExtensions": true, + "emitDeclarationOnly": true, "declaration": true, "esModuleInterop": true, + "resolveJsonModule": true, + "isolatedModules": true, "skipLibCheck": true, "baseUrl": ".", "composite": true, @@ -12,5 +17,8 @@ "outDir": "build" }, "include": ["src/**/*.ts", "test/**/*.spec.ts"], - "exclude": ["node_modules"] + "exclude": ["node_modules"], + "ts-node": { + "esm": true + } } diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..e2ec332 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globals: true, + }, +});