@ethereumjs/statemanager
Version:
An Ethereum statemanager implementation
791 lines (704 loc) • 26.9 kB
text/typescript
import { BinaryTree } from '@ethereumjs/binarytree'
import { BinaryTreeAccessedStateType } from '@ethereumjs/common'
import { RLP } from '@ethereumjs/rlp'
import type { Address, BinaryTreeExecutionWitness, PrefixedHexString } from '@ethereumjs/util'
import {
Account,
BINARY_TREE_CODE_CHUNK_SIZE,
BINARY_TREE_CODE_OFFSET,
BINARY_TREE_NODE_WIDTH,
BinaryTreeLeafType,
EthereumJSErrorWithoutCode,
KECCAK256_NULL,
MapDB,
bigIntToBytes,
bytesToBigInt,
bytesToHex,
chunkifyBinaryTreeCode,
createAddressFromString,
createPartialAccount,
createPartialAccountFromRLP,
decodeBinaryTreeLeafBasicData,
encodeBinaryTreeLeafBasicData,
equalsBytes,
generateBinaryTreeChunkSuffixes,
generateBinaryTreeCodeStems,
getBinaryTreeKeyForStorageSlot,
getBinaryTreeStem,
hexToBigInt,
hexToBytes,
padToEven,
setLengthLeft,
setLengthRight,
short,
unprefixedHexToBytes,
} from '@ethereumjs/util'
import { blake3 } from '@noble/hashes/blake3'
import debugDefault from 'debug'
import { keccak256 } from 'ethereum-cryptography/keccak.js'
import { OriginalStorageCache } from './cache/originalStorageCache.ts'
import { modifyAccountFields } from './util.ts'
import type {
AccountFields,
BinaryTreeAccessWitnessInterface,
BinaryTreeAccessedStateWithAddress,
GenesisState,
StateManagerInterface,
StorageDump,
StoragePair,
StorageRange,
} from '@ethereumjs/common'
import type { Debugger } from 'debug'
import type { Caches } from './cache/caches.ts'
import type { BinaryTreeState, StatefulBinaryTreeStateManagerOpts } from './types.ts'
const ZEROVALUE = '0x0000000000000000000000000000000000000000000000000000000000000000'
export class StatefulBinaryTreeStateManager implements StateManagerInterface {
protected _debug: Debugger
protected _caches?: Caches
preStateRoot: Uint8Array
originalStorageCache: OriginalStorageCache
hashFunction: (input: Uint8Array) => Uint8Array
protected _tree: BinaryTree
protected _checkpointCount: number
// Post-state provided from the executionWitness.
// Should not update. Used for comparing our computed post-state with the canonical one.
private _postState: BinaryTreeState = {}
/**
* StateManager is run in DEBUG mode (default: false)
* Taken from DEBUG environment variable
*
* Safeguards on debug() calls are added for
* performance reasons to avoid string literal evaluation
* @hidden
*/
protected readonly DEBUG: boolean = false
private keccakFunction: Function
constructor(opts: StatefulBinaryTreeStateManagerOpts) {
// Skip DEBUG calls unless 'ethjs' included in environmental DEBUG variables
// Additional window check is to prevent vite browser bundling (and potentially other) to break
this.DEBUG =
typeof window === 'undefined' ? (process?.env?.DEBUG?.includes('ethjs') ?? false) : false
this._checkpointCount = 0
if (opts.common?.isActivatedEIP(7864) === false) {
throw EthereumJSErrorWithoutCode('EIP-7864 required for binary tree state management')
}
this.hashFunction = opts.hashFunction ?? blake3
this._tree =
opts.tree ??
new BinaryTree({
hashFunction: this.hashFunction,
db: new MapDB<string, string | Uint8Array>(),
useRootPersistence: false,
cacheSize: 0,
})
this._debug = debugDefault('statemanager:binarytree')
this.originalStorageCache = new OriginalStorageCache(this.getStorage.bind(this))
this._caches = opts.caches
this.keccakFunction = keccak256
this.preStateRoot = new Uint8Array(32) // Initial state root is zeroes
}
/**
* Gets the account associated with `address` or `undefined` if account does not exist
* @param address - Address of the `account` to get
*/
getAccount = async (address: Address): Promise<Account | undefined> => {
const elem = this._caches?.account?.get(address)
if (elem !== undefined) {
return elem.accountRLP !== undefined
? createPartialAccountFromRLP(elem.accountRLP)
: undefined
}
const stem = getBinaryTreeStem(this.hashFunction, address, 0)
// First retrieve the account "header" values from the trie
const accountValues = await this._tree.get(stem, [
BinaryTreeLeafType.BasicData,
BinaryTreeLeafType.CodeHash,
])
let account
if (accountValues[0] !== null && accountValues[0] !== undefined) {
const basicData = decodeBinaryTreeLeafBasicData(accountValues[0]!)
account = createPartialAccount({
version: basicData.version,
balance: basicData.balance,
nonce: basicData.nonce,
// Codehash is either untouched (i.e. null) or deleted (i.e. overwritten with zeros)
codeHash:
accountValues[1] === null ||
accountValues[1] === undefined ||
equalsBytes(accountValues[1], new Uint8Array(32))
? KECCAK256_NULL
: accountValues[1],
codeSize: basicData.codeSize,
storageRoot: KECCAK256_NULL,
})
} else if (accountValues[1] === undefined || accountValues[1] === null) {
// account does not exist if both basic fields and codehash are undefined
if (this.DEBUG) {
this._debug(`getAccount address=${address.toString()} from DB (non-existent)`)
}
this._caches?.account?.put(address, account)
}
if (this.DEBUG) {
this._debug(`getAccount address=${address.toString()} stem=${short(stem)}`)
}
return account
}
public initBinaryTreeExecutionWitness(
_blockNum: bigint,
executionWitness?: BinaryTreeExecutionWitness | null,
) {
if (executionWitness === null || executionWitness === undefined) {
const errorMsg = `Invalid executionWitness=${executionWitness} for initBinaryTreeExecutionWitness`
this._debug(errorMsg)
throw Error(errorMsg)
}
this.preStateRoot = hexToBytes(executionWitness.parentStateRoot) // set prestate root
// Populate the post-state from the executionWitness
const postStateRaw = executionWitness.stateDiff.flatMap(({ stem, suffixDiffs }) => {
const suffixDiffPairs = suffixDiffs.map(({ newValue, currentValue, suffix }) => {
const key = `${stem}${padToEven(Number(suffix).toString(16))}` as PrefixedHexString
// A postState value of null means there was no change from the preState.
// In this implementation, we therefore replace null with the preState.
const value = newValue ?? currentValue
return {
[key]: value,
}
})
return suffixDiffPairs
})
const postState = postStateRaw.reduce((prevValue, currentValue) => {
const acc = { ...prevValue, ...currentValue }
return acc
}, {})
this._postState = postState
this._debug(`initBinaryTreeExecutionWitness postState=${JSON.stringify(this._postState)}`)
}
/**
* Saves an account into state under the provided `address`.
* @param address - Address under which to store `account`
* @param account - The account to store or undefined if to be deleted
*/
putAccount = async (address: Address, account?: Account): Promise<void> => {
if (this.DEBUG) {
this._debug(
`putAccount address=${address} nonce=${account?.nonce} balance=${
account?.balance
} contract=${account && account.isContract() ? 'yes' : 'no'} empty=${
account && account.isEmpty() ? 'yes' : 'no'
}`,
)
}
if (this._caches?.account === undefined) {
if (account !== undefined) {
const stem = getBinaryTreeStem(this.hashFunction, address, 0)
const basicDataBytes = encodeBinaryTreeLeafBasicData(account)
await this._tree.put(
stem,
[BinaryTreeLeafType.BasicData, BinaryTreeLeafType.CodeHash],
[basicDataBytes, account.codeHash],
)
} else {
// Delete account
await this.deleteAccount(address)
}
} else {
if (account !== undefined) {
this._caches?.account?.put(address, account, true)
} else {
this._caches?.account?.del(address)
}
}
}
/**
* Deletes an account from state under the provided `address`.
* @param address - Address of the account which should be deleted
*/
deleteAccount = async (address: Address): Promise<void> => {
if (this.DEBUG) {
this._debug(`Delete account ${address}`)
}
this._caches?.deleteAccount(address)
if (this._caches?.account === undefined) {
const stem = getBinaryTreeStem(this.hashFunction, address)
// Special instance where we delete the account and revert the trie value to untouched
await this._tree.put(
stem,
[BinaryTreeLeafType.BasicData, BinaryTreeLeafType.CodeHash],
[null, null],
)
}
}
modifyAccountFields = async (address: Address, accountFields: AccountFields): Promise<void> => {
await modifyAccountFields(this, address, accountFields)
}
putCode = async (address: Address, value: Uint8Array): Promise<void> => {
if (this.DEBUG) {
this._debug(`putCode address=${address.toString()} value=${short(value)}`)
}
this._caches?.code?.put(address, value)
const codeHash = keccak256(value)
if (equalsBytes(codeHash, KECCAK256_NULL)) {
// If the code hash is the null hash, no code has to be stored
return
}
if ((await this.getAccount(address)) === undefined) {
await this.putAccount(address, new Account())
}
if (this.DEBUG) {
this._debug(`Update codeHash (-> ${short(codeHash)}) for account ${address}`)
}
const codeChunks = chunkifyBinaryTreeCode(value)
const chunkStems = generateBinaryTreeCodeStems(codeChunks.length, address, this.hashFunction)
const chunkSuffixes: number[] = generateBinaryTreeChunkSuffixes(codeChunks.length)
// Put the code chunks corresponding to the first stem (up to 128 chunks)
await this._tree.put(
chunkStems[0],
chunkSuffixes.slice(
0,
chunkSuffixes.length <= BINARY_TREE_CODE_OFFSET
? chunkSuffixes.length
: BINARY_TREE_CODE_OFFSET,
),
codeChunks.slice(
0,
codeChunks.length <= BINARY_TREE_CODE_OFFSET ? codeChunks.length : BINARY_TREE_CODE_OFFSET,
),
)
// Put additional chunks under additional stems as applicable
for (let stem = 1; stem < chunkStems.length; stem++) {
const sliceStart = BINARY_TREE_CODE_OFFSET + BINARY_TREE_NODE_WIDTH * (stem - 1)
const sliceEnd =
value.length <= BINARY_TREE_CODE_OFFSET + BINARY_TREE_NODE_WIDTH * stem
? value.length
: BINARY_TREE_CODE_OFFSET + BINARY_TREE_NODE_WIDTH * stem
await this._tree.put(
chunkStems[stem],
chunkSuffixes.slice(sliceStart, sliceEnd),
codeChunks.slice(sliceStart, sliceEnd),
)
}
await this.modifyAccountFields(address, {
codeHash,
codeSize: value.length,
})
}
getCode = async (address: Address): Promise<Uint8Array> => {
if (this.DEBUG) {
this._debug(`getCode address=${address.toString()}`)
}
const elem = this._caches?.code?.get(address)
if (elem !== undefined) {
return elem.code ?? new Uint8Array(0)
}
const account = await this.getAccount(address)
if (!account) {
return new Uint8Array(0)
}
if (!account.isContract()) {
return new Uint8Array(0)
}
// allocate the code
const codeSize = account.codeSize
const stems = generateBinaryTreeCodeStems(
Math.ceil(codeSize / BINARY_TREE_CODE_CHUNK_SIZE),
address,
this.hashFunction,
)
const chunkSuffixes = generateBinaryTreeChunkSuffixes(
Math.ceil(codeSize / BINARY_TREE_CODE_CHUNK_SIZE),
)
const chunksByStem = new Array(stems.length)
// Retrieve the code chunks stored in the first leaf node
chunksByStem[0] = await this._tree.get(
stems[0],
chunkSuffixes.slice(
0,
codeSize <= BINARY_TREE_CODE_OFFSET ? codeSize : BINARY_TREE_CODE_OFFSET,
),
)
// Retrieve code chunks on any additional stems
for (let stem = 1; stem < stems.length; stem++) {
const sliceStart = BINARY_TREE_CODE_OFFSET + BINARY_TREE_NODE_WIDTH * (stem - 1)
const sliceEnd =
codeSize <= BINARY_TREE_CODE_OFFSET + BINARY_TREE_NODE_WIDTH * stem
? codeSize
: BINARY_TREE_CODE_OFFSET + BINARY_TREE_NODE_WIDTH * stem
chunksByStem[stem] = await this._tree.get(
stems[stem],
chunkSuffixes.slice(sliceStart, sliceEnd),
)
}
const chunks = chunksByStem.flat()
const code = new Uint8Array(codeSize)
// Insert code chunks into final array (skipping PUSHDATA overflow indicator byte)
for (let x = 0; x < chunks.length; x++) {
if (chunks[x] === undefined)
throw EthereumJSErrorWithoutCode(`expected code chunk at index ${x}, got undefined`)
let lastChunkByteIndex = BINARY_TREE_CODE_CHUNK_SIZE
// Determine code ending byte (if we're on the last chunk)
if (x === chunks.length - 1) {
// On the last chunk, the slice either ends on a partial chunk (if codeSize doesn't exactly fit in full chunks), or a full chunk
lastChunkByteIndex = codeSize % BINARY_TREE_CODE_CHUNK_SIZE || BINARY_TREE_CODE_CHUNK_SIZE
}
code.set(
chunks[x]!.slice(1, lastChunkByteIndex + 1),
code.byteOffset + x * BINARY_TREE_CODE_CHUNK_SIZE,
)
}
this._caches?.code?.put(address, code)
return code
}
getCodeSize = async (address: Address): Promise<number> => {
const accountBytes = (
await this._tree.get(getBinaryTreeStem(this.hashFunction, address), [
BinaryTreeLeafType.BasicData,
])
)[0]
if (accountBytes === null) return 0
return decodeBinaryTreeLeafBasicData(accountBytes).codeSize
}
getStorage = async (address: Address, key: Uint8Array): Promise<Uint8Array> => {
if (key.length !== 32) {
throw EthereumJSErrorWithoutCode('Storage key must be 32 bytes long')
}
const cachedValue = this._caches?.storage?.get(address, key)
if (cachedValue !== undefined) {
const decoded = RLP.decode(cachedValue ?? new Uint8Array(0)) as Uint8Array
return decoded
}
const account = await this.getAccount(address)
if (!account) {
return new Uint8Array()
}
const storageKey = getBinaryTreeKeyForStorageSlot(
address,
bytesToBigInt(key),
this.hashFunction,
)
const value = await this._tree.get(storageKey.slice(0, 31), [storageKey[31]])
this._caches?.storage?.put(address, key, value[0] ?? hexToBytes('0x80'))
const decoded = (value[0] ?? new Uint8Array(0)) as Uint8Array
return setLengthLeft(decoded, 32)
}
putStorage = async (address: Address, key: Uint8Array, value: Uint8Array): Promise<void> => {
this._caches?.storage?.put(address, key, RLP.encode(value))
if (this._caches?.storage === undefined) {
const storageKey = getBinaryTreeKeyForStorageSlot(
address,
bytesToBigInt(key),
this.hashFunction,
)
await this._tree.put(storageKey.slice(0, 31), [storageKey[31]], [setLengthLeft(value, 32)])
}
}
clearStorage = async (address: Address): Promise<void> => {
this._caches?.storage?.clearStorage(address)
}
checkpoint = async (): Promise<void> => {
this._tree.checkpoint()
this._caches?.checkpoint()
this._checkpointCount++
}
commit = async (): Promise<void> => {
await this._tree.commit()
this._caches?.commit()
this._checkpointCount--
if (this._checkpointCount === 0) {
await this.flush()
this.originalStorageCache.clear()
}
if (this.DEBUG) {
this._debug(`state checkpoint committed`)
}
}
revert = async (): Promise<void> => {
await this._tree.revert()
this._caches?.revert()
this._checkpointCount--
if (this._checkpointCount === 0) {
await this.flush()
this.originalStorageCache.clear()
}
}
flush = async (): Promise<void> => {
const codeItems = this._caches?.code?.flush() ?? []
for (const item of codeItems) {
const addr = createAddressFromString(`0x${item[0]}`)
const code = item[1].code
if (code === undefined) {
continue
}
await this.putCode(addr, code)
}
const storageItems = this._caches?.storage?.flush() ?? []
for (const item of storageItems) {
const address = createAddressFromString(`0x${item[0]}`)
const keyHex = item[1]
const keyBytes = unprefixedHexToBytes(keyHex)
const value = item[2]
const decoded = RLP.decode(value ?? new Uint8Array(0)) as Uint8Array
const account = await this.getAccount(address)
if (account) {
await this.putStorage(address, keyBytes, decoded)
}
}
const accountItems = this._caches?.account?.flush() ?? []
for (const item of accountItems) {
const address = createAddressFromString(`0x${item[0]}`)
const elem = item[1]
if (elem.accountRLP === undefined) {
await this.deleteAccount(address)
} else {
const account = createPartialAccountFromRLP(elem.accountRLP)
await this.putAccount(address, account)
}
}
}
async getComputedValue(
accessedState: BinaryTreeAccessedStateWithAddress,
): Promise<PrefixedHexString | null> {
const { address, type } = accessedState
switch (type) {
case BinaryTreeAccessedStateType.BasicData: {
if (this._caches === undefined) {
const accountData = await this.getAccount(address)
if (accountData === undefined) {
return null
}
const basicDataBytes = encodeBinaryTreeLeafBasicData(accountData)
return bytesToHex(basicDataBytes)
} else {
const encodedAccount = this._caches?.account?.get(address)?.accountRLP
if (encodedAccount === undefined) {
return null
}
const basicDataBytes = encodeBinaryTreeLeafBasicData(
createPartialAccountFromRLP(encodedAccount),
)
return bytesToHex(basicDataBytes)
}
}
case BinaryTreeAccessedStateType.CodeHash: {
if (this._caches === undefined) {
const accountData = await this.getAccount(address)
if (accountData === undefined) {
return null
}
return bytesToHex(accountData.codeHash)
} else {
const encodedAccount = this._caches?.account?.get(address)?.accountRLP
if (encodedAccount === undefined) {
return null
}
return bytesToHex(createPartialAccountFromRLP(encodedAccount).codeHash)
}
}
case BinaryTreeAccessedStateType.Code: {
const { codeOffset } = accessedState
let code: Uint8Array | undefined | null = null
if (this._caches === undefined) {
code = await this.getCode(address)
if (code === undefined) {
return null
}
} else {
code = this._caches?.code?.get(address)?.code
if (code === undefined) {
return null
}
}
// we can only compare the actual code because to compare the first byte would
// be very tricky and impossible in certain scenarios like when the previous code chunk
// was not accessed and hence not even provided in the witness
// We are left-padding with two zeroes to get a 32-byte length, but these bytes should not be considered reliable
return bytesToHex(
setLengthLeft(
setLengthRight(
code.slice(codeOffset, codeOffset + BINARY_TREE_CODE_CHUNK_SIZE),
BINARY_TREE_CODE_CHUNK_SIZE,
),
BINARY_TREE_CODE_CHUNK_SIZE + 1,
),
)
}
case BinaryTreeAccessedStateType.Storage: {
const { slot } = accessedState
const key = setLengthLeft(bigIntToBytes(slot), 32)
let storage: Uint8Array | undefined | null = null
if (this._caches === undefined) {
storage = await this.getStorage(address, key)
if (storage === undefined) {
return null
}
} else {
storage = this._caches?.storage?.get(address, key)
}
if (storage === undefined) {
return null
}
return bytesToHex(setLengthLeft(storage, 32))
}
}
}
// Verifies that the witness post-state matches the computed post-state
async verifyBinaryTreePostState(
accessWitness: BinaryTreeAccessWitnessInterface,
): Promise<boolean> {
// track what all chunks were accessed so as to compare in the end if any chunks were missed
// in access while comparing against the provided poststate in the execution witness
const accessedChunks = new Map<string, boolean>()
// switch to false if postVerify fails
let postFailures = 0
for (const accessedState of accessWitness?.accesses() ?? []) {
const { address, type } = accessedState
let extraMeta = ''
if (accessedState.type === BinaryTreeAccessedStateType.Code) {
extraMeta = `codeOffset=${accessedState.codeOffset}`
} else if (accessedState.type === BinaryTreeAccessedStateType.Storage) {
extraMeta = `slot=${accessedState.slot}`
}
const { chunkKey } = accessedState
accessedChunks.set(chunkKey, true)
let computedValue: PrefixedHexString | null | undefined =
await this.getComputedValue(accessedState)
if (computedValue === undefined) {
this.DEBUG &&
this._debug(
`Missing computed value for address=${address} type=${type} ${extraMeta} chunkKey=${chunkKey}`,
)
postFailures++
continue
}
let canonicalValue: PrefixedHexString | null | undefined = this._postState[chunkKey]
if (canonicalValue === undefined) {
this.DEBUG &&
this._debug(
`Block accesses missing from postState for address=${address} type=${type} ${extraMeta} chunkKey=${chunkKey}`,
)
postFailures++
continue
}
// if the access type is code, then we can't match the first byte because since the computed value
// doesn't has the first byte for push data since previous chunk code itself might not be available
if (accessedState.type === BinaryTreeAccessedStateType.Code) {
computedValue = computedValue !== null ? `0x${computedValue.slice(4)}` : null
canonicalValue = canonicalValue !== null ? `0x${canonicalValue.slice(4)}` : null
} else if (
accessedState.type === BinaryTreeAccessedStateType.Storage &&
canonicalValue === null &&
computedValue === ZEROVALUE
) {
canonicalValue = ZEROVALUE
}
this._debug(`computed ${computedValue} canonical ${canonicalValue}`)
if (computedValue !== canonicalValue) {
if (type === BinaryTreeAccessedStateType.BasicData) {
this.DEBUG &&
this._debug(
`canonical value: `,
canonicalValue === null
? null
: decodeBinaryTreeLeafBasicData(hexToBytes(canonicalValue)),
)
this.DEBUG &&
this._debug(
`computed value: `,
computedValue === null
? null
: decodeBinaryTreeLeafBasicData(hexToBytes(computedValue)),
)
}
this.DEBUG &&
this._debug(
`Block accesses mismatch address=${address} type=${type} ${extraMeta} chunkKey=${chunkKey}`,
)
this.DEBUG && this._debug(`expected=${canonicalValue}`)
this.DEBUG && this._debug(`computed=${computedValue}`)
postFailures++
}
}
for (const canChunkKey of Object.keys(this._postState)) {
if (accessedChunks.get(canChunkKey) === undefined) {
this.DEBUG && this._debug(`Missing chunk access for canChunkKey=${canChunkKey}`)
postFailures++
}
}
const verifyPassed = postFailures === 0
this.DEBUG &&
this._debug(
`verifyBinaryTreePostState verifyPassed=${verifyPassed} postFailures=${postFailures}`,
)
return verifyPassed
}
getStateRoot(): Promise<Uint8Array> {
return Promise.resolve(this._tree.root())
}
setStateRoot(stateRoot: Uint8Array, clearCache?: boolean): Promise<void> {
this._tree.root(stateRoot)
clearCache === true && this.clearCaches()
return Promise.resolve()
}
hasStateRoot(root: Uint8Array): Promise<boolean> {
return this._tree.checkRoot(root)
}
dumpStorage?(_address: Address): Promise<StorageDump> {
throw EthereumJSErrorWithoutCode('Method not implemented.')
}
dumpStorageRange?(_address: Address, _startKey: bigint, _limit: number): Promise<StorageRange> {
throw EthereumJSErrorWithoutCode('Method not implemented.')
}
clearCaches(): void {
this._caches?.clear()
}
shallowCopy(_downlevelCaches?: boolean): StateManagerInterface {
throw EthereumJSErrorWithoutCode('Method not implemented.')
}
async checkChunkWitnessPresent(_address: Address, _codeOffset: number): Promise<boolean> {
throw EthereumJSErrorWithoutCode('Method not implemented.')
}
async generateCanonicalGenesis(genesisState: GenesisState) {
await this._tree.createRootNode()
await this.checkpoint()
for (const addressStr of Object.keys(genesisState) as PrefixedHexString[]) {
const addrState = genesisState[addressStr]
let nonce: PrefixedHexString | undefined
let balance: PrefixedHexString | bigint
let code: PrefixedHexString | undefined
let storage: StoragePair[] | undefined = []
if (Array.isArray(addrState)) {
;[balance, code, storage, nonce] = addrState
} else {
balance = hexToBigInt(addrState)
nonce = '0x1'
code = '0x'
}
const address = createAddressFromString(addressStr)
await this.putAccount(address, new Account())
const codeBuf = hexToBytes(code ?? '0x')
const codeHash = this.keccakFunction(codeBuf)
// Set contract storage
if (storage !== undefined) {
for (const [storageKey, valHex] of storage) {
const val = hexToBytes(valHex)
if (['0x', '0x00'].includes(bytesToHex(val))) {
continue
}
const key = setLengthLeft(hexToBytes(storageKey), 32)
await this.putStorage(address, key, val)
}
}
// Put contract code
await this.putCode(address, codeBuf)
// Put account data
const account = createPartialAccount({
nonce,
balance,
codeHash,
codeSize: codeBuf.byteLength,
})
await this.putAccount(address, account)
}
await this.commit()
await this.flush()
}
}