diff --git a/packages/chopsticks/src/plugins/decode-key/index.ts b/packages/chopsticks/src/plugins/decode-key/index.ts index bf399cbf..975eb884 100644 --- a/packages/chopsticks/src/plugins/decode-key/index.ts +++ b/packages/chopsticks/src/plugins/decode-key/index.ts @@ -1,6 +1,6 @@ import { Config } from '../../schema' import { HexString } from '@polkadot/util/types' -import { decodeKey } from '../../utils/decoder' +import { decodeKey } from '@acala-network/chopsticks-core' import { defaultOptions } from '../../cli-options' import { setupContext } from '../../context' import type yargs from 'yargs' diff --git a/packages/chopsticks/src/plugins/dry-run/rpc.ts b/packages/chopsticks/src/plugins/dry-run/rpc.ts index 6495bcc7..27900394 100644 --- a/packages/chopsticks/src/plugins/dry-run/rpc.ts +++ b/packages/chopsticks/src/plugins/dry-run/rpc.ts @@ -78,10 +78,10 @@ export const rpc: Handler = async (context, [params]) => { if (raw) { return storageDiff } - const [oldData, newData, delta] = await decodeStorageDiff(context.chain.head, storageDiff) + const { oldState, newState, delta } = await decodeStorageDiff(context.chain.head, storageDiff) return { - old: oldData, - new: newData, + old: oldState, + new: newState, delta, } } diff --git a/packages/chopsticks/src/utils/decoder.ts b/packages/chopsticks/src/utils/decoder.ts index c71a5fec..c0789968 100644 --- a/packages/chopsticks/src/utils/decoder.ts +++ b/packages/chopsticks/src/utils/decoder.ts @@ -1,12 +1,6 @@ -import '@polkadot/types-codec' -import { Block } from '@acala-network/chopsticks-core' -import { DecoratedMeta } from '@polkadot/types/metadata/decorate/types' +import { Block, decodeBlockStorageDiff } from '@acala-network/chopsticks-core' import { HexString } from '@polkadot/util/types' -import { StorageEntry } from '@polkadot/types/primitive/types' -import { StorageKey } from '@polkadot/types' -import { blake2AsHex } from '@polkadot/util-crypto' import { create } from 'jsondiffpatch' -import { hexToU8a, u8aToHex } from '@polkadot/util' import _ from 'lodash' const diffPatcher = create({ @@ -14,102 +8,11 @@ const diffPatcher = create({ textDiff: { minLength: Number.MAX_VALUE }, // skip text diff }) -const _CACHE: Record> = {} - -const getCache = (uid: string): Record => { - if (!_CACHE[uid]) { - _CACHE[uid] = {} - } - return _CACHE[uid] -} - -const getStorageEntry = (meta: DecoratedMeta, block: Block, key: HexString) => { - const cache = getCache(block.chain.uid) - for (const [prefix, storageEntry] of Object.entries(cache)) { - if (key.startsWith(prefix)) return storageEntry - } - for (const module of Object.values(meta.query)) { - for (const storage of Object.values(module)) { - const keyPrefix = u8aToHex(storage.keyPrefix()) - if (key.startsWith(keyPrefix)) { - cache[keyPrefix] = storage - return storage - } - } - } - return undefined -} - -export const decodeKey = ( - meta: DecoratedMeta, - block: Block, - key: HexString, -): { storage?: StorageEntry; decodedKey?: StorageKey } => { - const storage = getStorageEntry(meta, block, key) - const decodedKey = meta.registry.createType('StorageKey', key) - if (storage) { - decodedKey.setMeta(storage.meta) - return { storage, decodedKey } - } - return {} -} - -export const decodeKeyValue = (meta: DecoratedMeta, block: Block, key: HexString, value?: HexString | null) => { - const { storage, decodedKey } = decodeKey(meta, block, key) - - if (!storage || !decodedKey) { - return { [key]: value } - } - - const decodeValue = () => { - if (!value) return null - if (storage.section === 'substrate' && storage.method === 'code') { - return `:code blake2_256 ${blake2AsHex(value, 256)} (${hexToU8a(value).length} bytes)` - } - return meta.registry.createType(decodedKey.outputType, hexToU8a(value)).toHuman() - } - - switch (decodedKey.args.length) { - case 2: { - return { - [storage.section]: { - [storage.method]: { - [decodedKey.args[0].toString()]: { - [decodedKey.args[1].toString()]: decodeValue(), - }, - }, - }, - } - } - case 1: { - return { - [storage.section]: { - [storage.method]: { - [decodedKey.args[0].toString()]: decodeValue(), - }, - }, - } - } - default: - return { - [storage.section]: { - [storage.method]: decodeValue(), - }, - } - } -} - export const decodeStorageDiff = async (block: Block, diff: [HexString, HexString | null][]) => { - const oldState = {} - const newState = {} - const meta = await block.meta - for (const [key, value] of diff) { - _.merge(oldState, decodeKeyValue(meta, block, key, (await block.get(key)) as any)) - _.merge(newState, decodeKeyValue(meta, block, key, value)) - } + const [oldState, newState] = await decodeBlockStorageDiff(block, diff) const oldStateWithoutEvents = _.cloneDeep(oldState) if (oldStateWithoutEvents['system']?.['events']) { oldStateWithoutEvents['system']['events'] = [] } - return [oldState, newState, diffPatcher.diff(oldStateWithoutEvents, newState)] + return { oldState, newState, delta: diffPatcher.diff(oldStateWithoutEvents, newState) } } diff --git a/packages/chopsticks/src/utils/generate-html-diff.ts b/packages/chopsticks/src/utils/generate-html-diff.ts index 839032a7..9f08ede8 100644 --- a/packages/chopsticks/src/utils/generate-html-diff.ts +++ b/packages/chopsticks/src/utils/generate-html-diff.ts @@ -6,9 +6,9 @@ import { template } from 'lodash' import path from 'node:path' export const generateHtmlDiff = async (block: Block, diff: [HexString, HexString | null][]) => { - const [left, _right, delta] = await decodeStorageDiff(block, diff) + const { oldState, delta } = await decodeStorageDiff(block, diff) const htmlTemplate = readFileSync(path.join(__dirname, '../../template/diff.html'), 'utf-8') - return template(htmlTemplate)({ left: JSON.stringify(left), delta: JSON.stringify(delta) }) + return template(htmlTemplate)({ left: JSON.stringify(oldState), delta: JSON.stringify(delta) }) } export const generateHtmlDiffPreviewFile = async ( diff --git a/packages/core/src/utils/decoder.ts b/packages/core/src/utils/decoder.ts new file mode 100644 index 00000000..7f73710c --- /dev/null +++ b/packages/core/src/utils/decoder.ts @@ -0,0 +1,111 @@ +import '@polkadot/types-codec' +import { Block } from '../blockchain/block' +import { DecoratedMeta } from '@polkadot/types/metadata/decorate/types' +import { HexString } from '@polkadot/util/types' +import { StorageEntry } from '@polkadot/types/primitive/types' +import { StorageKey } from '@polkadot/types' +import { blake2AsHex } from '@polkadot/util-crypto' +import { hexToU8a, u8aToHex } from '@polkadot/util' +import _ from 'lodash' + +const _CACHE: Record> = {} + +const getCache = (uid: string): Map => { + if (!_CACHE[uid]) { + _CACHE[uid] = new Map() + } + return _CACHE[uid] +} + +const getStorageEntry = (meta: DecoratedMeta, block: Block, key: HexString) => { + const cache = getCache(block.chain.uid) + for (const [prefix, storageEntry] of cache.entries()) { + if (key.startsWith(prefix)) return storageEntry + } + for (const module of Object.values(meta.query)) { + for (const storage of Object.values(module)) { + const keyPrefix = u8aToHex(storage.keyPrefix()) + if (key.startsWith(keyPrefix)) { + cache.set(keyPrefix, storage) + return storage + } + } + } + return undefined +} + +export const decodeKey = ( + meta: DecoratedMeta, + block: Block, + key: HexString, +): { storage?: StorageEntry; decodedKey?: StorageKey } => { + const storage = getStorageEntry(meta, block, key) + const decodedKey = meta.registry.createType('StorageKey', key) + if (storage) { + decodedKey.setMeta(storage.meta) + return { storage, decodedKey } + } + return {} +} + +export const decodeKeyValue = (meta: DecoratedMeta, block: Block, key: HexString, value?: HexString | null) => { + const { storage, decodedKey } = decodeKey(meta, block, key) + + if (!storage || !decodedKey) { + return { [key]: value } + } + + const decodeValue = () => { + if (!value) return null + if (storage.section === 'substrate' && storage.method === 'code') { + return `:code blake2_256 ${blake2AsHex(value, 256)} (${hexToU8a(value).length} bytes)` + } + return meta.registry.createType(decodedKey.outputType, hexToU8a(value)).toHuman() + } + + switch (decodedKey.args.length) { + case 2: { + return { + [storage.section]: { + [storage.method]: { + [decodedKey.args[0].toString()]: { + [decodedKey.args[1].toString()]: decodeValue(), + }, + }, + }, + } + } + case 1: { + return { + [storage.section]: { + [storage.method]: { + [decodedKey.args[0].toString()]: decodeValue(), + }, + }, + } + } + default: + return { + [storage.section]: { + [storage.method]: decodeValue(), + }, + } + } +} + +/** + * Decode block storage diff + * @param block Block to compare storage diff + * @param diff Storage diff + * @returns decoded old state and new state + */ +export const decodeBlockStorageDiff = async (block: Block, diff: [HexString, HexString | null][]) => { + const oldState = {} + const newState = {} + const meta = await block.meta + for (const [key, value] of diff) { + _.merge(oldState, decodeKeyValue(meta, block, key, (await block.get(key)) as any)) + _.merge(newState, decodeKeyValue(meta, block, key, value)) + } + return [oldState, newState] +} diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index 67cdcfd6..574dc85a 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -6,6 +6,7 @@ import { Blockchain } from '../blockchain' export * from './set-storage' export * from './time-travel' +export * from './decoder' export type GetKeys = (startKey?: string) => Promise[]> diff --git a/packages/e2e/src/decoder.test.ts b/packages/e2e/src/decoder.test.ts index 16be82e5..e18d3c28 100644 --- a/packages/e2e/src/decoder.test.ts +++ b/packages/e2e/src/decoder.test.ts @@ -1,5 +1,5 @@ import { afterAll, describe, expect, it } from 'vitest' -import { decodeKey, decodeKeyValue } from '@acala-network/chopsticks/utils/decoder' +import { decodeKey, decodeKeyValue } from '@acala-network/chopsticks-core/utils/decoder' import networks from './networks'