@acala-network/chopsticks 0.8.5-5 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/chopsticks.js +1 -1
  2. package/{lib → dist/cjs}/plugins/dry-run/dry-run-preimage.js +1 -3
  3. package/{lib → dist/cjs}/plugins/run-block/index.js +15 -21
  4. package/{lib → dist/cjs}/utils/generate-html-diff.js +1 -1
  5. package/dist/esm/cli-options.js +40 -0
  6. package/dist/esm/cli.js +90 -0
  7. package/dist/esm/context.js +86 -0
  8. package/dist/esm/plugins/decode-key/index.js +23 -0
  9. package/dist/esm/plugins/dry-run/cli.js +42 -0
  10. package/dist/esm/plugins/dry-run/dry-run-extrinsic.js +29 -0
  11. package/dist/esm/plugins/dry-run/dry-run-preimage.js +92 -0
  12. package/dist/esm/plugins/dry-run/rpc.js +101 -0
  13. package/dist/esm/plugins/index.js +25 -0
  14. package/dist/esm/plugins/new-block/index.js +67 -0
  15. package/dist/esm/plugins/run-block/index.js +176 -0
  16. package/dist/esm/plugins/set-block-build-mode/index.js +25 -0
  17. package/dist/esm/plugins/set-head/index.js +31 -0
  18. package/dist/esm/plugins/set-runtime-log-level/index.js +24 -0
  19. package/dist/esm/plugins/set-storage/index.js +38 -0
  20. package/dist/esm/plugins/time-travel/index.js +23 -0
  21. package/dist/esm/plugins/try-runtime/index.js +54 -0
  22. package/{lib/plugins/types.d.ts → dist/esm/plugins/types.js} +3 -3
  23. package/dist/esm/rpc/index.js +26 -0
  24. package/dist/esm/schema/index.js +53 -0
  25. package/dist/esm/server.js +164 -0
  26. package/dist/esm/setup-with-server.js +22 -0
  27. package/dist/esm/types.js +2 -0
  28. package/dist/esm/utils/decoder.js +15 -0
  29. package/dist/esm/utils/generate-html-diff.js +16 -0
  30. package/dist/esm/utils/open-html.js +5 -0
  31. package/dist/esm/utils/override.js +43 -0
  32. package/dist/esm/utils/tunnel.js +11 -0
  33. package/dist/types/index.d.ts +4 -0
  34. package/dist/types/logger.d.ts +1 -0
  35. package/dist/types/plugins/dry-run/index.d.ts +2 -0
  36. package/{lib → dist/types}/plugins/run-block/index.d.ts +14 -6
  37. package/dist/types/plugins/types.d.ts +11 -0
  38. package/dist/types/utils/index.d.ts +4 -0
  39. package/package.json +23 -14
  40. /package/{lib → dist/cjs}/cli-options.js +0 -0
  41. /package/{lib → dist/cjs}/cli.js +0 -0
  42. /package/{lib → dist/cjs}/context.js +0 -0
  43. /package/{lib → dist/cjs}/index.js +0 -0
  44. /package/{lib → dist/cjs}/logger.js +0 -0
  45. /package/{lib → dist/cjs}/plugins/decode-key/index.js +0 -0
  46. /package/{lib → dist/cjs}/plugins/dry-run/cli.js +0 -0
  47. /package/{lib → dist/cjs}/plugins/dry-run/dry-run-extrinsic.js +0 -0
  48. /package/{lib → dist/cjs}/plugins/dry-run/index.js +0 -0
  49. /package/{lib → dist/cjs}/plugins/dry-run/rpc.js +0 -0
  50. /package/{lib → dist/cjs}/plugins/index.js +0 -0
  51. /package/{lib → dist/cjs}/plugins/new-block/index.js +0 -0
  52. /package/{lib → dist/cjs}/plugins/set-block-build-mode/index.js +0 -0
  53. /package/{lib → dist/cjs}/plugins/set-head/index.js +0 -0
  54. /package/{lib → dist/cjs}/plugins/set-runtime-log-level/index.js +0 -0
  55. /package/{lib → dist/cjs}/plugins/set-storage/index.js +0 -0
  56. /package/{lib → dist/cjs}/plugins/time-travel/index.js +0 -0
  57. /package/{lib → dist/cjs}/plugins/try-runtime/index.js +0 -0
  58. /package/{lib → dist/cjs}/plugins/types.js +0 -0
  59. /package/{lib → dist/cjs}/rpc/index.js +0 -0
  60. /package/{lib → dist/cjs}/schema/index.js +0 -0
  61. /package/{lib → dist/cjs}/server.js +0 -0
  62. /package/{lib → dist/cjs}/setup-with-server.js +0 -0
  63. /package/{lib → dist/cjs}/types.js +0 -0
  64. /package/{lib → dist/cjs}/utils/decoder.js +0 -0
  65. /package/{lib → dist/cjs}/utils/index.js +0 -0
  66. /package/{lib → dist/cjs}/utils/open-html.js +0 -0
  67. /package/{lib → dist/cjs}/utils/override.js +0 -0
  68. /package/{lib → dist/cjs}/utils/tunnel.js +0 -0
  69. /package/{lib/index.d.ts → dist/esm/index.js} +0 -0
  70. /package/{lib/logger.d.ts → dist/esm/logger.js} +0 -0
  71. /package/{lib/plugins/dry-run/index.d.ts → dist/esm/plugins/dry-run/index.js} +0 -0
  72. /package/{lib/utils/index.d.ts → dist/esm/utils/index.js} +0 -0
  73. /package/{lib → dist/types}/cli-options.d.ts +0 -0
  74. /package/{lib → dist/types}/cli.d.ts +0 -0
  75. /package/{lib → dist/types}/context.d.ts +0 -0
  76. /package/{lib → dist/types}/plugins/decode-key/index.d.ts +0 -0
  77. /package/{lib → dist/types}/plugins/dry-run/cli.d.ts +0 -0
  78. /package/{lib → dist/types}/plugins/dry-run/dry-run-extrinsic.d.ts +0 -0
  79. /package/{lib → dist/types}/plugins/dry-run/dry-run-preimage.d.ts +0 -0
  80. /package/{lib → dist/types}/plugins/dry-run/rpc.d.ts +0 -0
  81. /package/{lib → dist/types}/plugins/index.d.ts +0 -0
  82. /package/{lib → dist/types}/plugins/new-block/index.d.ts +0 -0
  83. /package/{lib → dist/types}/plugins/set-block-build-mode/index.d.ts +0 -0
  84. /package/{lib → dist/types}/plugins/set-head/index.d.ts +0 -0
  85. /package/{lib → dist/types}/plugins/set-runtime-log-level/index.d.ts +0 -0
  86. /package/{lib → dist/types}/plugins/set-storage/index.d.ts +0 -0
  87. /package/{lib → dist/types}/plugins/time-travel/index.d.ts +0 -0
  88. /package/{lib → dist/types}/plugins/try-runtime/index.d.ts +0 -0
  89. /package/{lib → dist/types}/rpc/index.d.ts +0 -0
  90. /package/{lib → dist/types}/schema/index.d.ts +0 -0
  91. /package/{lib → dist/types}/server.d.ts +0 -0
  92. /package/{lib → dist/types}/setup-with-server.d.ts +0 -0
  93. /package/{lib → dist/types}/types.d.ts +0 -0
  94. /package/{lib → dist/types}/utils/decoder.d.ts +0 -0
  95. /package/{lib → dist/types}/utils/generate-html-diff.d.ts +0 -0
  96. /package/{lib → dist/types}/utils/open-html.d.ts +0 -0
  97. /package/{lib → dist/types}/utils/override.d.ts +0 -0
  98. /package/{lib → dist/types}/utils/tunnel.d.ts +0 -0
package/chopsticks.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- require('./lib/cli.js')
2
+ require('./dist/cjs/cli.js')
@@ -69,9 +69,7 @@ const dryRunPreimage = async (argv) => {
69
69
  if ('Error' in result) {
70
70
  throw new Error(result.Error);
71
71
  }
72
- for (const logs of result.Call.runtimeLogs) {
73
- logger_1.defaultLogger.info(`RuntimeLogs:\n${logs}`);
74
- }
72
+ (0, chopsticks_core_1.printRuntimeLogs)(result.Call.runtimeLogs);
75
73
  const filePath = await (0, generate_html_diff_1.generateHtmlDiffPreviewFile)(block, result.Call.storageDiff, hash);
76
74
  console.log(`Generated preview ${filePath}`);
77
75
  if (argv['open']) {
@@ -1,14 +1,9 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.rpc = exports.name = exports.cli = void 0;
7
4
  const node_fs_1 = require("node:fs");
8
5
  const zod_1 = require("zod");
9
- const lodash_1 = __importDefault(require("lodash"));
10
6
  const chopsticks_core_1 = require("@acala-network/chopsticks-core");
11
- const logger_1 = require("../../logger");
12
7
  const cli_options_1 = require("../../cli-options");
13
8
  const generate_html_diff_1 = require("../../utils/generate-html-diff");
14
9
  const open_html_1 = require("../../utils/open-html");
@@ -50,9 +45,7 @@ const cli = (y) => {
50
45
  if ('Error' in result) {
51
46
  throw new Error(result.Error);
52
47
  }
53
- for (const logs of result.Call.runtimeLogs) {
54
- logger_1.defaultLogger.info(`RuntimeLogs:\n${logs}`);
55
- }
48
+ (0, chopsticks_core_1.printRuntimeLogs)(result.Call.runtimeLogs);
56
49
  if (argv.html) {
57
50
  const filePath = await (0, generate_html_diff_1.generateHtmlDiffPreviewFile)(parent, result.Call.storageDiff, block.hash);
58
51
  console.log(`Generated preview ${filePath}`);
@@ -100,6 +93,7 @@ const rpc = async ({ chain }, [params]) => {
100
93
  const registry = await parentBlock.registry;
101
94
  const header = registry.createType('Header', block.header);
102
95
  const wasm = await parentBlock.wasm;
96
+ const meta = await parentBlock.meta;
103
97
  const blockNumber = parentBlock.number + 1;
104
98
  const hash = `0x${Math.round(Math.random() * 100000000)
105
99
  .toString(16)
@@ -112,6 +106,8 @@ const rpc = async ({ chain }, [params]) => {
112
106
  const resp = {
113
107
  phases: [],
114
108
  };
109
+ // exclude system events because it can be stupidly large and redudant
110
+ const systemEventsKey = (0, chopsticks_core_1.compactHex)(meta.query.system.events());
115
111
  const run = async (fn, args) => {
116
112
  const result = await (0, chopsticks_core_1.runTask)({
117
113
  wasm,
@@ -123,23 +119,21 @@ const rpc = async ({ chain }, [params]) => {
123
119
  if ('Error' in result) {
124
120
  throw new Error(result.Error);
125
121
  }
126
- const resp = {};
122
+ const resp = { storageDiff: [] };
127
123
  const raw = result.Call.storageDiff;
128
124
  newBlock.pushStorageLayer().setAll(raw);
129
- if (includeRawStorage) {
130
- resp.raw = raw;
131
- }
132
- if (includeParsed) {
133
- const meta = await newBlock.meta;
134
- const parsed = {};
135
- for (const [key, value] of raw) {
136
- lodash_1.default.merge(parsed, (0, chopsticks_core_1.decodeKeyValue)(meta, newBlock, key, value, false));
125
+ for (const [key, value] of raw) {
126
+ if (key === systemEventsKey) {
127
+ continue;
128
+ }
129
+ const obj = {};
130
+ if (includeRawStorage) {
131
+ obj.raw = { key, value };
137
132
  }
138
- // clear events because it can be stupidly large and redudant
139
- if (parsed['system']?.['events']) {
140
- delete parsed['system']['events'];
133
+ if (includeParsed) {
134
+ obj.parsed = (0, chopsticks_core_1.decodeKeyValue)(await newBlock.meta, newBlock, key, value, false);
141
135
  }
142
- resp.parsed = parsed;
136
+ resp.storageDiff.push(obj);
143
137
  }
144
138
  resp.logs = result.Call.runtimeLogs;
145
139
  return resp;
@@ -10,7 +10,7 @@ const lodash_1 = require("lodash");
10
10
  const node_path_1 = __importDefault(require("node:path"));
11
11
  const generateHtmlDiff = async (block, diff) => {
12
12
  const { oldState, delta } = await (0, decoder_1.decodeStorageDiff)(block, diff);
13
- const htmlTemplate = (0, node_fs_1.readFileSync)(node_path_1.default.join(__dirname, '../../template/diff.html'), 'utf-8');
13
+ const htmlTemplate = (0, node_fs_1.readFileSync)(node_path_1.default.join(__dirname, '../../../template/diff.html'), 'utf-8');
14
14
  return (0, lodash_1.template)(htmlTemplate)({ left: JSON.stringify(oldState), delta: JSON.stringify(delta) });
15
15
  };
16
16
  exports.generateHtmlDiff = generateHtmlDiff;
@@ -0,0 +1,40 @@
1
+ export const defaultOptions = {
2
+ endpoint: {
3
+ desc: 'Endpoint to connect to',
4
+ string: true,
5
+ },
6
+ block: {
7
+ desc: 'Block hash or block number. Default to latest block',
8
+ string: true,
9
+ },
10
+ 'wasm-override': {
11
+ desc: 'Path to wasm override',
12
+ string: true,
13
+ },
14
+ db: {
15
+ desc: 'Path to database',
16
+ string: true,
17
+ },
18
+ config: {
19
+ desc: 'Path to config file with default options',
20
+ string: true,
21
+ },
22
+ 'runtime-log-level': {
23
+ desc: 'Runtime maximum log level [off = 0; error = 1; warn = 2; info = 3; debug = 4; trace = 5]',
24
+ number: true,
25
+ },
26
+ 'offchain-worker': {
27
+ desc: 'Enable offchain worker',
28
+ boolean: true,
29
+ },
30
+ };
31
+ export const mockOptions = {
32
+ 'import-storage': {
33
+ desc: 'Pre-defined JSON/YAML storage file path',
34
+ string: true,
35
+ },
36
+ 'mock-signature-host': {
37
+ desc: 'Mock signature host so any signature starts with 0xdeadbeef and filled by 0xcd is considered valid',
38
+ boolean: true,
39
+ },
40
+ };
@@ -0,0 +1,90 @@
1
+ import { config as dotenvConfig } from 'dotenv';
2
+ import { hideBin } from 'yargs/helpers';
3
+ import _ from 'lodash';
4
+ import yargs from 'yargs';
5
+ import { BuildBlockMode, connectParachains, connectVertical } from '@acala-network/chopsticks-core';
6
+ import { fetchConfig } from './schema';
7
+ import { defaultOptions, mockOptions } from './cli-options';
8
+ import { pluginExtendCli } from './plugins';
9
+ import { setupWithServer } from '.';
10
+ dotenvConfig();
11
+ const processArgv = async (argv) => {
12
+ if (argv.config) {
13
+ Object.assign(argv, _.defaults(argv, await fetchConfig(argv.config)));
14
+ }
15
+ argv.port = argv.port ?? (process.env.PORT ? Number(process.env.PORT) : 8000);
16
+ };
17
+ const commands = yargs(hideBin(process.argv))
18
+ .scriptName('chopsticks')
19
+ .middleware(processArgv, false)
20
+ .command('*', 'Dev mode, fork off a chain', (yargs) => yargs.options({
21
+ ...defaultOptions,
22
+ ...mockOptions,
23
+ port: {
24
+ desc: 'Port to listen on',
25
+ number: true,
26
+ },
27
+ 'build-block-mode': {
28
+ desc: 'Build block mode. Default to Batch',
29
+ enum: [BuildBlockMode.Batch, BuildBlockMode.Manual, BuildBlockMode.Instant],
30
+ },
31
+ 'allow-unresolved-imports': {
32
+ desc: 'Allow wasm unresolved imports',
33
+ boolean: true,
34
+ },
35
+ 'max-memory-block-count': {
36
+ desc: 'Max memory block count',
37
+ number: true,
38
+ },
39
+ resume: {
40
+ desc: `Resume from the specified block hash or block number in db.
41
+ If true, it will resume from the latest block in db.
42
+ Note this will override the block option`,
43
+ string: true,
44
+ },
45
+ }), async (argv) => {
46
+ await setupWithServer(argv);
47
+ })
48
+ .command('xcm', 'XCM setup with relaychain and parachains', (yargs) => yargs
49
+ .options({
50
+ relaychain: {
51
+ desc: 'Relaychain config file path',
52
+ string: true,
53
+ },
54
+ parachain: {
55
+ desc: 'Parachain config file path',
56
+ type: 'array',
57
+ string: true,
58
+ required: true,
59
+ },
60
+ })
61
+ .alias('relaychain', 'r')
62
+ .alias('parachain', 'p'), async (argv) => {
63
+ const parachains = [];
64
+ for (const config of argv.parachain) {
65
+ const { chain } = await setupWithServer(await fetchConfig(config));
66
+ parachains.push(chain);
67
+ }
68
+ if (parachains.length > 1) {
69
+ await connectParachains(parachains);
70
+ }
71
+ if (argv.relaychain) {
72
+ const { chain: relaychain } = await setupWithServer(await fetchConfig(argv.relaychain));
73
+ for (const parachain of parachains) {
74
+ await connectVertical(relaychain, parachain);
75
+ }
76
+ }
77
+ })
78
+ .strict()
79
+ .help()
80
+ .alias('help', 'h')
81
+ .alias('version', 'v')
82
+ .alias('config', 'c')
83
+ .alias('endpoint', 'e')
84
+ .alias('port', 'p')
85
+ .alias('block', 'b')
86
+ .alias('import-storage', 's')
87
+ .alias('wasm-override', 'w')
88
+ .usage('Usage: $0 <command> [options]')
89
+ .example('$0', '-c acala');
90
+ pluginExtendCli(commands).then(() => commands.parse());
@@ -0,0 +1,86 @@
1
+ import './utils/tunnel';
2
+ import { GenesisProvider, defaultLogger, isUrl, setup, timeTravel } from '@acala-network/chopsticks-core';
3
+ import { SqliteDatabase } from '@acala-network/chopsticks-db';
4
+ import { overrideStorage, overrideWasm } from './utils/override';
5
+ import axios from 'axios';
6
+ const logger = defaultLogger.child({ name: 'setup-context' });
7
+ export const genesisFromUrl = async (url) => {
8
+ const getFile = async (url) => {
9
+ if (isUrl(url)) {
10
+ return axios.get(url).then((x) => x.data);
11
+ }
12
+ else if (typeof process === 'object') {
13
+ const { lstatSync, readFileSync } = await import('node:fs');
14
+ if (lstatSync(url).isFile()) {
15
+ return JSON.parse(String(readFileSync(url)));
16
+ }
17
+ }
18
+ throw Error(`invalid genesis path or url ${url}`);
19
+ };
20
+ return new GenesisProvider(await getFile(url));
21
+ };
22
+ export const setupContext = async (argv, overrideParent = false) => {
23
+ let genesis;
24
+ if (argv.genesis) {
25
+ if (typeof argv.genesis === 'string') {
26
+ genesis = await genesisFromUrl(argv.genesis);
27
+ }
28
+ else {
29
+ genesis = new GenesisProvider(argv.genesis);
30
+ }
31
+ }
32
+ const chain = await setup({
33
+ endpoint: argv.endpoint,
34
+ block: argv.block,
35
+ genesis,
36
+ buildBlockMode: argv['build-block-mode'],
37
+ db: argv.db ? new SqliteDatabase(argv.db) : undefined,
38
+ mockSignatureHost: argv['mock-signature-host'],
39
+ allowUnresolvedImports: argv['allow-unresolved-imports'],
40
+ runtimeLogLevel: argv['runtime-log-level'],
41
+ registeredTypes: argv['registered-types'],
42
+ offchainWorker: argv['offchain-worker'],
43
+ maxMemoryBlockCount: argv['max-memory-block-count'],
44
+ });
45
+ // load block from db
46
+ if (chain.db) {
47
+ if (argv.resume) {
48
+ let blockData = null;
49
+ if (typeof argv.resume === 'string' && argv.resume.startsWith('0x')) {
50
+ blockData = await chain.db.queryBlock(argv.resume);
51
+ }
52
+ else if (typeof argv.resume === 'boolean' || argv.resume === 'true') {
53
+ blockData = await chain.db.queryHighestBlock();
54
+ }
55
+ else if (Number.isInteger(+argv.resume)) {
56
+ blockData = await chain.db.queryBlockByNumber(+argv.resume);
57
+ }
58
+ else {
59
+ throw new Error(`Resume failed. Invalid resume option ${argv.resume}`);
60
+ }
61
+ if (blockData) {
62
+ const block = await chain.loadBlockFromDB(blockData.number);
63
+ block && (await chain.setHead(block));
64
+ logger.info(`Resume from block ${blockData.number}, hash: ${blockData.hash}`);
65
+ }
66
+ else {
67
+ throw new Error(`Resume failed. Cannot find block ${argv.resume}`);
68
+ }
69
+ }
70
+ }
71
+ if (argv.timestamp)
72
+ await timeTravel(chain, argv.timestamp);
73
+ let at;
74
+ if (overrideParent) {
75
+ // in case of run block we need to apply wasm-override and import-storage to parent block
76
+ const block = await chain.head.parentBlock;
77
+ if (!block)
78
+ throw new Error('Cannot find parent block');
79
+ at = block.hash;
80
+ }
81
+ // override wasm before importing storage, in case new pallets have been
82
+ // added that have storage imports
83
+ await overrideStorage(chain, argv['import-storage'], at);
84
+ await overrideWasm(chain, argv['wasm-override'], at);
85
+ return { chain };
86
+ };
@@ -0,0 +1,23 @@
1
+ import { decodeKey } from '@acala-network/chopsticks-core';
2
+ import { defaultOptions } from '../../cli-options';
3
+ import { setupContext } from '../../context';
4
+ export const cli = (y) => {
5
+ y.command('decode-key <key>', 'Deocde a key', (yargs) => yargs
6
+ .positional('key', {
7
+ desc: 'Key to decode',
8
+ type: 'string',
9
+ })
10
+ .options({
11
+ ...defaultOptions,
12
+ }), async (argv) => {
13
+ const context = await setupContext(argv);
14
+ const { storage, decodedKey } = decodeKey(await context.chain.head.meta, context.chain.head, argv.key);
15
+ if (storage && decodedKey) {
16
+ console.log(`${storage.section}.${storage.method}`, decodedKey.args.map((x) => JSON.stringify(x.toHuman())).join(', '));
17
+ }
18
+ else {
19
+ console.log('Unknown');
20
+ }
21
+ process.exit(0);
22
+ });
23
+ };
@@ -0,0 +1,42 @@
1
+ import { defaultOptions, mockOptions } from '../../cli-options';
2
+ import { dryRunExtrinsic } from './dry-run-extrinsic';
3
+ import { dryRunPreimage } from './dry-run-preimage';
4
+ export const cli = (y) => {
5
+ y.command('dry-run', 'Dry run an extrinsic', (yargs) => yargs.options({
6
+ ...defaultOptions,
7
+ ...mockOptions,
8
+ extrinsic: {
9
+ desc: 'Extrinsic or call to dry run. If you pass call here then address is required to fake signature',
10
+ string: true,
11
+ },
12
+ address: {
13
+ desc: 'Address to fake sign extrinsic',
14
+ string: true,
15
+ },
16
+ preimage: {
17
+ desc: 'Preimage to dry run',
18
+ string: true,
19
+ },
20
+ at: {
21
+ desc: 'Block hash to dry run',
22
+ string: true,
23
+ },
24
+ 'output-path': {
25
+ desc: 'File path to print output',
26
+ string: true,
27
+ },
28
+ html: {
29
+ desc: 'Generate html with storage diff',
30
+ },
31
+ open: {
32
+ desc: 'Open generated html',
33
+ },
34
+ }), async (argv) => {
35
+ if (argv.preimage) {
36
+ await dryRunPreimage(argv);
37
+ }
38
+ else {
39
+ await dryRunExtrinsic(argv);
40
+ }
41
+ });
42
+ };
@@ -0,0 +1,29 @@
1
+ import { blake2AsHex } from '@polkadot/util-crypto';
2
+ import { writeFileSync } from 'node:fs';
3
+ import { defaultLogger } from '../../logger';
4
+ import { generateHtmlDiffPreviewFile } from '../../utils/generate-html-diff';
5
+ import { openHtml } from '../../utils/open-html';
6
+ import { setupContext } from '../../context';
7
+ export const dryRunExtrinsic = async (argv) => {
8
+ const context = await setupContext(argv);
9
+ const input = argv['address'] ? { call: argv['extrinsic'], address: argv['address'] } : argv['extrinsic'];
10
+ const { outcome, storageDiff } = await context.chain.dryRunExtrinsic(input, argv['at']);
11
+ if (outcome.isErr) {
12
+ throw new Error(outcome.asErr.toString());
13
+ }
14
+ defaultLogger.info(outcome.toHuman(), 'dry_run_outcome');
15
+ if (argv['html']) {
16
+ const filePath = await generateHtmlDiffPreviewFile(context.chain.head, storageDiff, blake2AsHex(argv['extrinsic'], 256));
17
+ console.log(`Generated preview ${filePath}`);
18
+ if (argv['open']) {
19
+ openHtml(filePath);
20
+ }
21
+ }
22
+ else if (argv['output-path']) {
23
+ writeFileSync(argv['output-path'], JSON.stringify({ outcome: outcome.toHuman(), storageDiff }, null, 2));
24
+ }
25
+ else {
26
+ console.dir({ outcome: outcome.toHuman(), storageDiff }, { depth: null, colors: false });
27
+ }
28
+ process.exit(0);
29
+ };
@@ -0,0 +1,92 @@
1
+ import { blake2AsHex } from '@polkadot/util-crypto';
2
+ import { hexToU8a } from '@polkadot/util';
3
+ import { defaultLogger } from '../../logger';
4
+ import { generateHtmlDiffPreviewFile } from '../../utils/generate-html-diff';
5
+ import { newHeader, printRuntimeLogs, runTask, setStorage, taskHandler } from '@acala-network/chopsticks-core';
6
+ import { openHtml } from '../../utils/open-html';
7
+ import { setupContext } from '../../context';
8
+ export const dryRunPreimage = async (argv) => {
9
+ const context = await setupContext(argv);
10
+ const extrinsic = argv['preimage'];
11
+ const block = context.chain.head;
12
+ const registry = await block.registry;
13
+ const header = await newHeader(block);
14
+ const data = hexToU8a(extrinsic);
15
+ const hash = blake2AsHex(data, 256);
16
+ await setStorage(context.chain, {
17
+ Preimage: {
18
+ PreimageFor: [[[[hash, data.byteLength]], extrinsic]],
19
+ StatusFor: [
20
+ [
21
+ [hash],
22
+ {
23
+ Requested: {
24
+ count: 1,
25
+ len: data.byteLength,
26
+ },
27
+ },
28
+ ],
29
+ ],
30
+ },
31
+ Scheduler: {
32
+ Agenda: [
33
+ [
34
+ [block.number + 1],
35
+ [
36
+ {
37
+ maybeId: '0x64656d6f637261633a0000000000000000000000000000000000000000000000',
38
+ priority: 63,
39
+ call: {
40
+ Lookup: {
41
+ hash: hash,
42
+ len: data.byteLength,
43
+ },
44
+ },
45
+ origin: { system: { Root: null } },
46
+ },
47
+ ],
48
+ ],
49
+ ],
50
+ Lookup: [[['0x64656d6f637261633a0000000000000000000000000000000000000000000000'], [block.number + 1, 0]]],
51
+ },
52
+ });
53
+ const calls = [['Core_initialize_block', [header.toHex()]]];
54
+ for (const inherent of await block.chain.getInherents()) {
55
+ calls.push(['BlockBuilder_apply_extrinsic', [inherent]]);
56
+ }
57
+ calls.push(['BlockBuilder_finalize_block', []]);
58
+ defaultLogger.info({ preimage: registry.createType('Call', data).toHuman() }, 'Dry run preimage');
59
+ const result = await runTask({
60
+ wasm: await block.wasm,
61
+ calls,
62
+ mockSignatureHost: false,
63
+ allowUnresolvedImports: false,
64
+ runtimeLogLevel: argv['runtime-log-level'] || 0,
65
+ }, taskHandler(block));
66
+ if ('Error' in result) {
67
+ throw new Error(result.Error);
68
+ }
69
+ printRuntimeLogs(result.Call.runtimeLogs);
70
+ const filePath = await generateHtmlDiffPreviewFile(block, result.Call.storageDiff, hash);
71
+ console.log(`Generated preview ${filePath}`);
72
+ if (argv['open']) {
73
+ openHtml(filePath);
74
+ }
75
+ // if dry-run preimage has extrinsic arguments then dry-run extrinsic
76
+ // this is usefull to test something after preimage is applied
77
+ if (argv['extrinsic']) {
78
+ await context.chain.newBlock();
79
+ const input = argv['address'] ? { call: argv['extrinsic'], address: argv['address'] } : argv['extrinsic'];
80
+ const { outcome, storageDiff } = await context.chain.dryRunExtrinsic(input);
81
+ if (outcome.isErr) {
82
+ throw new Error(outcome.asErr.toString());
83
+ }
84
+ defaultLogger.info(outcome.toHuman(), 'dry_run_outcome');
85
+ const filePath = await generateHtmlDiffPreviewFile(context.chain.head, storageDiff, blake2AsHex(argv['extrinsic'], 256));
86
+ console.log(`Generated preview ${filePath}`);
87
+ if (argv['open']) {
88
+ openHtml(filePath);
89
+ }
90
+ }
91
+ process.exit(0);
92
+ };
@@ -0,0 +1,101 @@
1
+ import { z } from 'zod';
2
+ import { ResponseError } from '@acala-network/chopsticks-core';
3
+ import { decodeStorageDiff } from '../../utils/decoder';
4
+ import { generateHtmlDiff } from '../../utils/generate-html-diff';
5
+ const zHex = z.custom((val) => /^0x\w+$/.test(val));
6
+ const zHash = z.string().length(66).and(zHex);
7
+ const zParaId = z.string().regex(/^\d+$/).transform(Number);
8
+ const schema = z.object({
9
+ raw: z.boolean().optional(),
10
+ html: z.boolean().optional(),
11
+ extrinsic: zHex
12
+ .or(z.object({
13
+ call: zHex,
14
+ address: zHex,
15
+ }))
16
+ .optional(),
17
+ hrmp: z
18
+ .record(zParaId, z
19
+ .array(z.object({
20
+ sentAt: z.number(),
21
+ data: zHex,
22
+ }))
23
+ .min(1))
24
+ .optional(),
25
+ dmp: z
26
+ .array(z.object({
27
+ sentAt: z.number(),
28
+ msg: zHex,
29
+ }))
30
+ .min(1)
31
+ .optional(),
32
+ ump: z.record(zParaId, z.array(zHex).min(1)).optional(),
33
+ at: zHash.optional(),
34
+ });
35
+ // custom rpc name (optional). e.g. dryRun will be called as dev_dryRun
36
+ export const name = 'dryRun';
37
+ /**
38
+ * Dry run an extrinsic or messages.
39
+ * If `html` is true, return the generated storage diff html string.
40
+ * If `raw` is true, return the raw storage diff.
41
+ * Otherwise, return `{ oldState, newState, delta }`.
42
+ *
43
+ * This function is a dev rpc handler. Use `dev_dryRun` as the method name when calling it.
44
+ *
45
+ * @param context - The context object of the rpc handler
46
+ * @param params - The parameters of the rpc handler
47
+ *
48
+ * @example Dry run an dmp
49
+ * ```ts
50
+ * import { WsProvider } from '@polkadot/rpc-provider'
51
+ * const ws = new WsProvider(`ws://localhost:8000`)
52
+ * const params = [
53
+ {
54
+ raw: false,
55
+ dmp: [
56
+ // https://acala.subscan.io/xcm_message/polkadot-2ab22918c567455af3563989d852f307f4cc1250
57
+ {
58
+ sentAt: 14471353,
59
+ msg: '0x02100104000100000b00280b9bba030a13000100000b00280b9bba03010300286bee0d0100040001010070c53d8e216f9c0f2e3b11c53f5f4bf3e078b995d5f0ed590f889f41e20e6531',
60
+ },
61
+ ],
62
+ },
63
+ ]
64
+ * await ws.send('dev_dryRun', params)
65
+ * ```
66
+ */
67
+ export const rpc = async (context, [params]) => {
68
+ const { html, extrinsic, hrmp, dmp, ump, raw, at } = schema.parse(params);
69
+ const dryRun = async () => {
70
+ if (extrinsic) {
71
+ const { outcome, storageDiff } = await context.chain.dryRunExtrinsic(extrinsic, at);
72
+ if (outcome.isErr) {
73
+ throw new ResponseError(1, outcome.asErr.toString());
74
+ }
75
+ return storageDiff;
76
+ }
77
+ if (hrmp) {
78
+ return context.chain.dryRunHrmp(hrmp, at);
79
+ }
80
+ if (dmp) {
81
+ return context.chain.dryRunDmp(dmp, at);
82
+ }
83
+ if (ump) {
84
+ return context.chain.dryRunUmp(ump, at);
85
+ }
86
+ throw new ResponseError(1, 'No extrinsic to run');
87
+ };
88
+ const storageDiff = await dryRun();
89
+ if (html) {
90
+ return generateHtmlDiff(context.chain.head, storageDiff);
91
+ }
92
+ if (raw) {
93
+ return storageDiff;
94
+ }
95
+ const { oldState, newState, delta } = await decodeStorageDiff(context.chain.head, storageDiff);
96
+ return {
97
+ old: oldState,
98
+ new: newState,
99
+ delta,
100
+ };
101
+ };
@@ -0,0 +1,25 @@
1
+ import { camelCase } from 'lodash';
2
+ import { lstatSync, readdirSync } from 'fs';
3
+ import { defaultLogger } from '../logger';
4
+ const logger = defaultLogger.child({ name: 'plugin' });
5
+ export const pluginHandlers = {};
6
+ const plugins = readdirSync(__dirname).filter((file) => lstatSync(`${__dirname}/${file}`).isDirectory());
7
+ (async () => {
8
+ for (const plugin of plugins) {
9
+ const { rpc, name } = await import(`./${plugin}`);
10
+ if (rpc) {
11
+ const methodName = name || camelCase(plugin);
12
+ pluginHandlers[`dev_${methodName}`] = rpc;
13
+ logger.debug(`Registered plugin ${plugin} RPC`);
14
+ }
15
+ }
16
+ })();
17
+ export const pluginExtendCli = async (y) => {
18
+ for (const plugin of plugins) {
19
+ const { cli } = await import(`./${plugin}`);
20
+ if (cli) {
21
+ cli(y);
22
+ logger.debug(`Registered plugin ${plugin} CLI`);
23
+ }
24
+ }
25
+ };