@tevm/actions
Version:
A typesafe library for writing forge scripts in typescript
101 lines (96 loc) • 3.54 kB
JavaScript
import { InternalError } from '@tevm/errors'
import { bytesToHex, numberToHex } from '@tevm/utils'
import { getPendingClient } from '../internal/getPendingClient.js'
import { maybeThrowOnFail } from '../internal/maybeThrowOnFail.js'
/**
* Converts TevmState with BigInt values to SerializableTevmState with Hex strings
* @param {import('@tevm/state').TevmState} state - The state to convert
* @returns {import('@tevm/state').SerializableTevmState} The serializable state
*/
const serializeState = (state) => {
/** @type {import('@tevm/state').SerializableTevmState} */
const serializedState = {}
for (const [address, account] of Object.entries(state)) {
serializedState[address] = {
nonce: numberToHex(account.nonce),
balance: numberToHex(account.balance),
storageRoot: account.storageRoot,
codeHash: account.codeHash,
...(account.deployedBytecode && { deployedBytecode: account.deployedBytecode }),
...(account.storage && {
storage: {
...Object.fromEntries(Object.entries(account.storage).map(([key, value]) => [`0x${key}`, value])),
},
}),
}
}
return serializedState
}
/**
* Creates a handler for dumping the TEVM state.
*
* @param {import("@tevm/node").TevmNode} client - The TEVM client instance.
* @param {object} [options] - Optional settings.
* @param {boolean} [options.throwOnFail] - Whether to throw an error if the state dump fails.
* @returns {import('../DumpState/DumpStateHandlerType.js').DumpStateHandler} - The state dump handler function.
*
* @example
* ```typescript
* import { createTevmNode } from 'tevm/node'
* import { dumpStateHandler } from 'tevm/actions'
*
* const client = createTevmNode()
*
* const dumpState = dumpStateHandler(client)
*
* const { state, errors } = await dumpState()
* if (errors) {
* console.error(errors)
* } else {
* console.log(state)
* }
* ```
*/
export const dumpStateHandler =
(client, options = {}) =>
async ({ throwOnFail = options.throwOnFail, blockTag = 'latest' } = {}) => {
try {
const vm =
blockTag === 'pending'
? await getPendingClient(client).then((mineResult) => {
if (mineResult.errors) {
throw mineResult.errors[0]
}
return mineResult.pendingClient.getVm()
})
: await client.getVm()
if ('dumpCanonicalGenesis' in vm.stateManager) {
if (blockTag === 'latest' || blockTag === 'pending') {
const rawState = await vm.stateManager.dumpCanonicalGenesis()
return { state: serializeState(rawState) }
}
const block = await vm.blockchain.getBlockByTag(blockTag)
if (await vm.stateManager.hasStateRoot(block.header.stateRoot)) {
const rawState = vm.stateManager._baseState.stateRoots.get(bytesToHex(block.header.stateRoot)) ?? {}
return { state: serializeState(rawState) }
}
client.logger.warn(`State root does not exist for block ${blockTag}. Returning empty state`)
return { state: {} }
}
throw new InternalError(
'Unsupported state manager. Must use a TEVM state manager from `@tevm/state` package. This may indicate a bug in TEVM internal code.',
)
} catch (e) {
if (/** @type {import('@tevm/errors').BaseError}*/ (e)._tag) {
return maybeThrowOnFail(throwOnFail ?? true, {
state: {},
// TODO we need to strongly type errors better here
errors: [/**@type {any} */ (e)],
})
}
return maybeThrowOnFail(throwOnFail ?? true, {
state: {},
errors: [e instanceof InternalError ? e : new InternalError('Unexpected error', { cause: e })],
})
}
}