@ethereumjs/statemanager
Version:
An Ethereum statemanager implementation
155 lines (131 loc) • 5.5 kB
text/typescript
import { Account, EthereumJSErrorWithoutCode, bytesToHex } from '@ethereumjs/util'
import { keccak_256 } from '@noble/hashes/sha3.js'
import { OriginalStorageCache } from './cache/originalStorageCache.ts'
import { modifyAccountFields } from './util.ts'
import type { AccountFields, Common, StateManagerInterface } from '@ethereumjs/common'
import type { Address, PrefixedHexString } from '@ethereumjs/util'
import type { SimpleStateManagerOpts } from './index.ts'
/**
* Simple and dependency-free state manager for basic state access use cases
* where a merkle-patricia or binary tree backed state manager is too heavy-weight.
*
* This state manager comes with the basic state access logic for
* accounts, storage and code (put* and get* methods) as well as a simple
* implementation of checkpointing but lacks methods implementations of
* state root related logic as well as some other non-core functions.
*
* Functionality provided is sufficient to be used for simple EVM use
* cases and the state manager is used as default there.
*
* For a more full fledged and MPT-backed state manager implementation
* have a look at the [`@ethereumjs/statemanager` package docs](https://github.com/ethereumjs/ethereumjs-monorepo/blob/master/packages/statemanager/docs/README.md).
*/
export class SimpleStateManager implements StateManagerInterface {
public accountStack: Map<PrefixedHexString, Account | undefined>[] = []
public codeStack: Map<PrefixedHexString, Uint8Array>[] = []
public storageStack: Map<string, Uint8Array>[] = []
originalStorageCache: {
get(address: Address, key: Uint8Array): Promise<Uint8Array>
clear(): void
}
public readonly common?: Common
constructor(opts: SimpleStateManagerOpts = {}) {
this.checkpointSync()
this.originalStorageCache = new OriginalStorageCache(this.getStorage.bind(this))
this.common = opts.common
}
protected topAccountStack() {
return this.accountStack[this.accountStack.length - 1]
}
protected topCodeStack() {
return this.codeStack[this.codeStack.length - 1]
}
protected topStorageStack() {
return this.storageStack[this.storageStack.length - 1]
}
// Synchronous version of checkpoint() to allow to call from constructor
protected checkpointSync() {
const newTopA = new Map(this.topAccountStack())
for (const [address, account] of newTopA) {
const accountCopy =
account !== undefined
? Object.assign(Object.create(Object.getPrototypeOf(account)), account)
: undefined
newTopA.set(address, accountCopy)
}
this.accountStack.push(newTopA)
this.codeStack.push(new Map(this.topCodeStack()))
this.storageStack.push(new Map(this.topStorageStack()))
}
async getAccount(address: Address): Promise<Account | undefined> {
return this.topAccountStack().get(address.toString())
}
async putAccount(address: Address, account?: Account | undefined): Promise<void> {
this.topAccountStack().set(address.toString(), account)
}
async deleteAccount(address: Address): Promise<void> {
this.topAccountStack().set(address.toString(), undefined)
}
async modifyAccountFields(address: Address, accountFields: AccountFields): Promise<void> {
await modifyAccountFields(this, address, accountFields)
}
async getCode(address: Address): Promise<Uint8Array> {
return this.topCodeStack().get(address.toString()) ?? new Uint8Array(0)
}
async putCode(address: Address, value: Uint8Array): Promise<void> {
this.topCodeStack().set(address.toString(), value)
if ((await this.getAccount(address)) === undefined) {
await this.putAccount(address, new Account())
}
await this.modifyAccountFields(address, {
codeHash: (this.common?.customCrypto.keccak256 ?? keccak_256)(value),
})
}
async getCodeSize(address: Address): Promise<number> {
const contractCode = await this.getCode(address)
return contractCode.length
}
async getStorage(address: Address, key: Uint8Array): Promise<Uint8Array> {
return (
this.topStorageStack().get(`${address.toString()}_${bytesToHex(key)}`) ?? new Uint8Array(0)
)
}
async putStorage(address: Address, key: Uint8Array, value: Uint8Array): Promise<void> {
this.topStorageStack().set(`${address.toString()}_${bytesToHex(key)}`, value)
}
async clearStorage(): Promise<void> {}
async checkpoint(): Promise<void> {
this.checkpointSync()
}
async commit(): Promise<void> {
this.accountStack.splice(-2, 1)
this.codeStack.splice(-2, 1)
this.storageStack.splice(-2, 1)
}
async revert(): Promise<void> {
this.accountStack.pop()
this.codeStack.pop()
this.storageStack.pop()
}
async flush(): Promise<void> {}
clearCaches(): void {}
shallowCopy(): StateManagerInterface {
const copy = new SimpleStateManager({ common: this.common })
for (let i = 0; i < this.accountStack.length; i++) {
copy.accountStack.push(new Map(this.accountStack[i]))
copy.codeStack.push(new Map(this.codeStack[i]))
copy.storageStack.push(new Map(this.storageStack[i]))
}
return copy
}
// State root functionality not implemented
getStateRoot(): Promise<Uint8Array> {
throw EthereumJSErrorWithoutCode('Method not implemented.')
}
setStateRoot(): Promise<void> {
throw EthereumJSErrorWithoutCode('Method not implemented.')
}
hasStateRoot(): Promise<boolean> {
throw EthereumJSErrorWithoutCode('Method not implemented.')
}
}