diff --git a/package-lock.json b/package-lock.json index 8fb0c5fe17..7d78164924 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16787,7 +16787,8 @@ "version": "4.3.0", "license": "MIT", "dependencies": { - "@ethereumjs/util": "^9.0.3" + "@ethereumjs/util": "^9.0.3", + "ethereum-cryptography": "^2.2.1" }, "devDependencies": { "@polkadot/util": "^12.6.2", diff --git a/packages/common/package.json b/packages/common/package.json index 73af2edef5..7e817c1f8d 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -57,7 +57,8 @@ "tsc": "../../config/cli/ts-compile.sh" }, "dependencies": { - "@ethereumjs/util": "^9.0.3" + "@ethereumjs/util": "^9.0.3", + "ethereum-cryptography": "^2.2.1" }, "devDependencies": { "@polkadot/util": "^12.6.2", diff --git a/packages/evm/src/evm.ts b/packages/evm/src/evm.ts index a9266197e9..9cb4b6611c 100644 --- a/packages/evm/src/evm.ts +++ b/packages/evm/src/evm.ts @@ -1,5 +1,5 @@ import { Chain, Common, Hardfork } from '@ethereumjs/common' -import { DefaultStateManager } from '@ethereumjs/statemanager' +import { SimpleStateManager } from '@ethereumjs/statemanager' import { Account, Address, @@ -168,7 +168,7 @@ export class EVM implements EVMInterface { } if (opts.stateManager === undefined) { - opts.stateManager = new DefaultStateManager() + opts.stateManager = new SimpleStateManager() } return new EVM(opts, bn128) diff --git a/packages/evm/src/types.ts b/packages/evm/src/types.ts index f423302e33..fa420aa964 100644 --- a/packages/evm/src/types.ts +++ b/packages/evm/src/types.ts @@ -279,7 +279,13 @@ export interface EVMOpts { bls?: EVMBLSInterface /* - * The StateManager which is used to update the trie + * The EVM comes with a basic dependency-minimized `SimpleStateManager` implementation + * which serves most code execution use cases and which is included in the + * `@ethereumjs/statemanager` package. + * + * The `@ethereumjs/statemanager` package also provides a variety of state manager + * implementations for different needs (MPT-tree backed, RPC, experimental verkle) + * which can be used by this option as a replacement. */ stateManager?: EVMStateManagerInterface diff --git a/packages/evm/test/eips/eip-3860.spec.ts b/packages/evm/test/eips/eip-3860.spec.ts index 84560c38fc..e46a3bd6d0 100644 --- a/packages/evm/test/eips/eip-3860.spec.ts +++ b/packages/evm/test/eips/eip-3860.spec.ts @@ -1,5 +1,4 @@ import { Chain, Common, Hardfork } from '@ethereumjs/common' -import { DefaultStateManager } from '@ethereumjs/statemanager' import { Address, concatBytes, equalsBytes, hexToBytes, privateToAddress } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' @@ -17,7 +16,6 @@ describe('EIP 3860 tests', () => { }) const evm = await EVM.create({ common, - stateManager: new DefaultStateManager(), }) const buffer = new Uint8Array(1000000).fill(0x60) @@ -58,11 +56,9 @@ describe('EIP 3860 tests', () => { const caller = Address.fromString('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b') const evm = await EVM.create({ common: commonWith3860, - stateManager: new DefaultStateManager(), }) const evmWithout3860 = await EVM.create({ common: commonWithout3860, - stateManager: new DefaultStateManager(), }) const contractFactory = Address.fromString('0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b') const contractAccount = await evm.stateManager.getAccount(contractFactory) @@ -104,11 +100,9 @@ describe('EIP 3860 tests', () => { const caller = Address.fromString('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b') const evm = await EVM.create({ common: commonWith3860, - stateManager: new DefaultStateManager(), }) const evmWithout3860 = await EVM.create({ common: commonWithout3860, - stateManager: new DefaultStateManager(), }) const contractFactory = Address.fromString('0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b') const contractAccount = await evm.stateManager.getAccount(contractFactory) @@ -143,8 +137,6 @@ describe('EIP 3860 tests', () => { }) const evm = await EVM.create({ common, - stateManager: new DefaultStateManager(), - allowUnlimitedInitCodeSize: true, }) @@ -179,14 +171,11 @@ describe('EIP 3860 tests', () => { for (const code of ['F0', 'F5']) { const evm = await EVM.create({ common: commonWith3860, - stateManager: new DefaultStateManager(), allowUnlimitedInitCodeSize: true, }) const evmDisabled = await EVM.create({ common: commonWith3860, - stateManager: new DefaultStateManager(), - allowUnlimitedInitCodeSize: false, }) const contractFactory = Address.fromString('0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b') diff --git a/packages/statemanager/README.md b/packages/statemanager/README.md index 6b74a9dbd6..85e194be10 100644 --- a/packages/statemanager/README.md +++ b/packages/statemanager/README.md @@ -17,17 +17,20 @@ To obtain the latest version, simply require the project using `npm`: npm install @ethereumjs/statemanager ``` -Note: this library was part of the [@ethereumjs/vm](../vm/) package up till VM `v5`. - ## Usage ### Introduction The `StateManager` provides high-level access and manipulation methods to and for the Ethereum state, thinking in terms of accounts or contract code rather then the storage operations of the underlying data structure (e.g. a [Trie](../trie/)). -The library includes a TypeScript interface `StateManager` to ensure a unified interface (e.g. when passed to the VM), a concrete Trie-based `DefaultStateManager` implementation, as well as an `RPCStateManager` implementation that sources state and history data from an external JSON-RPC provider. +This library includes several different implementations that all implement the `StateManager` interface which is accepted by the `vm` library. These include: + +- [`SimpleStateManager`](./src/simpleStateManager.ts) -a minimally functional (and dependency minimized) version of the state manager suitable for most basic EVM bytecode operations +- [`DefaultStateManager`](./src//stateManager.ts) - a Merkle-Patricia Trie-based `DefaultStateManager` implementation that is used by the `@ethereumjs/client` and `@ethereumjs/vm` +- [`RPCStateManager`](./src/rpcStateManager.ts) - a light-weight implementation that sources state and history data from an external JSON-RPC provider +- [`StatelessVerkleStateManager`](./src/statelessVerkleStateManager.ts) - an experimental implementation of a "stateless" state manager that uses Verkle proofs to provide necessary state access for processing verkle-trie based blocks -It also includes a checkpoint/revert/commit mechanism to either persist or revert state changes and provides a sophisticated caching mechanism under the hood to reduce the need for direct state accesses. +It also includes a checkpoint/revert/commit mechanism to either persist or revert state changes and provides a sophisticated caching mechanism under the hood to reduce the need reading state accesses from disk. ### `DefaultStateManager` @@ -69,6 +72,31 @@ Caches now "survive" a flush operation and especially long-lived usage scenarios Have a loot at the extended `CacheOptions` on how to use and leverage the new cache system. +### `SimpleStateManager` + +The `SimpleStateManager` is a dependency-minimized simple state manager implementation. While this state manager implementation lacks the implementations of some non-core functionality as well as proof related logic (e.g. `setStateRoot()`) it is suitable for a lot use cases where things like sophisticated caching or state root handling is not needed. + +This state manager can be instantiated and used as follows: + +```ts +// ./examples/simple.ts + +import { SimpleStateManager } from '../src/index.js' +import { Account, Address, randomBytes } from '@ethereumjs/util' + +const main = async () => { + const sm = new SimpleStateManager() + const address = Address.fromPrivateKey(randomBytes(32)) + const account = new Account(0n, 0xfffffn) + await sm.putAccount(address, account) + console.log(await sm.getAccount(address)) +} + +main() +``` + +### `DefaultStateManager` -> Proofs + #### Instantiating from a Proof The `DefaultStateManager` has a static constructor `fromProof` that accepts one or more [EIP-1186](https://eips.ethereum.org/EIPS/eip-1186) [proofs](./src/stateManager.ts) and will instantiate a `DefaultStateManager` with a partial trie containing the state provided by the proof(s). Be aware that this constructor accepts the `StateManagerOpts` dictionary as a third parameter (i.e. `stateManager.fromProof(proof, safe, opts)`). Therefore, if you need to use a customized trie (e.g. one that does not use key hashing) or specify caching options, you can pass them in here. If you do instantiate a trie and pass it into the `fromProof` constructor, you also need to instantiate the trie using the corresponding `fromProof` constructor to ensure the state root matches when the proof data is added to the trie. See [this test](./test/stateManager.spec.ts#L287-L288) for more details. @@ -174,7 +202,7 @@ const main = async () => { const blockchain = new RPCBlockChain(provider) const blockTag = 1n const state = new RPCStateManager({ provider, blockTag }) - const evm = new EVM({ blockchain, stateManager: state }) // note that evm is ready to run BLOCKHASH opcodes (over RPC) + const evm = await EVM.create({ blockchain, stateManager: state }) // note that evm is ready to run BLOCKHASH opcodes (over RPC) } catch (e) { console.log(e.message) // fetch would fail because provider url is not real. please replace provider with a valid rpc url string. } diff --git a/packages/statemanager/examples/simple.ts b/packages/statemanager/examples/simple.ts new file mode 100644 index 0000000000..b7618b4127 --- /dev/null +++ b/packages/statemanager/examples/simple.ts @@ -0,0 +1,12 @@ +import { SimpleStateManager } from '../src/index.js' +import { Account, Address, randomBytes } from '@ethereumjs/util' + +const main = async () => { + const sm = new SimpleStateManager() + const address = Address.fromPrivateKey(randomBytes(32)) + const account = new Account(0n, 0xfffffn) + await sm.putAccount(address, account) + console.log(await sm.getAccount(address)) +} + +main() diff --git a/packages/statemanager/src/cache/originalStorageCache.ts b/packages/statemanager/src/cache/originalStorageCache.ts index 50c6169e91..dd837be4e3 100644 --- a/packages/statemanager/src/cache/originalStorageCache.ts +++ b/packages/statemanager/src/cache/originalStorageCache.ts @@ -4,6 +4,15 @@ import type { Address } from '@ethereumjs/util' type getContractStorage = (address: Address, key: Uint8Array) => Promise +/** + * Helper class to cache original storage values (so values already being present in + * the pre-state of a call), mainly for correct gas cost calculation in EVM/VM. + * + * TODO: Usage of this class is very implicit through the injected `getContractStorage()` + * method bound to the calling state manager. It should be examined if there are alternative + * designs being more transparent and direct along the next breaking release round. + * + */ export class OriginalStorageCache { private map: Map> private getContractStorage: getContractStorage diff --git a/packages/statemanager/src/index.ts b/packages/statemanager/src/index.ts index 099da76970..d6d3d9c56a 100644 --- a/packages/statemanager/src/index.ts +++ b/packages/statemanager/src/index.ts @@ -1,5 +1,6 @@ export * from './accessWitness.js' export * from './cache/index.js' export * from './rpcStateManager.js' +export * from './simpleStateManager.js' export * from './statelessVerkleStateManager.js' export * from './stateManager.js' diff --git a/packages/statemanager/src/simpleStateManager.ts b/packages/statemanager/src/simpleStateManager.ts new file mode 100644 index 0000000000..f9976f3f71 --- /dev/null +++ b/packages/statemanager/src/simpleStateManager.ts @@ -0,0 +1,198 @@ +import { Account, bytesToHex } from '@ethereumjs/util' +import { keccak256 } from 'ethereum-cryptography/keccak.js' + +import { OriginalStorageCache } from './cache/originalStorageCache.js' + +import type { + AccountFields, + Common, + EVMStateManagerInterface, + Proof, + StorageDump, + StorageRange, +} from '@ethereumjs/common' +import type { Address, PrefixedHexString } from '@ethereumjs/util' + +/** + * Options for constructing a {@link SimpleStateManager}. + */ +export interface SimpleStateManagerOpts { + /** + * The common to use + */ + common?: Common +} + +/** + * Simple and dependency-free state manager for basic state access use cases + * where a merkle-patricia or verkle 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. + */ +export class SimpleStateManager implements EVMStateManagerInterface { + public accountStack: Map[] = [] + public codeStack: Map[] = [] + public storageStack: Map[] = [] + + originalStorageCache: { + get(address: Address, key: Uint8Array): Promise + clear(): void + } + + public readonly common?: Common + + constructor(opts: SimpleStateManagerOpts = {}) { + this.checkpointSync() + this.originalStorageCache = new OriginalStorageCache(this.getContractStorage.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 { + return this.topAccountStack().get(address.toString()) + } + + async putAccount(address: Address, account?: Account | undefined): Promise { + this.topAccountStack().set(address.toString(), account) + } + + async deleteAccount(address: Address): Promise { + this.topAccountStack().set(address.toString(), undefined) + } + + async modifyAccountFields(address: Address, accountFields: AccountFields): Promise { + let account = await this.getAccount(address) + if (!account) { + account = new Account() + } + account.nonce = accountFields.nonce ?? account.nonce + account.balance = accountFields.balance ?? account.balance + account.storageRoot = accountFields.storageRoot ?? account.storageRoot + account.codeHash = accountFields.codeHash ?? account.codeHash + await this.putAccount(address, account) + } + + async getContractCode(address: Address): Promise { + return this.topCodeStack().get(address.toString()) ?? new Uint8Array(0) + } + + async putContractCode(address: Address, value: Uint8Array): Promise { + 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 ?? keccak256)(value), + }) + } + + async getContractCodeSize(address: Address): Promise { + const contractCode = await this.getContractCode(address) + return contractCode.length + } + + async getContractStorage(address: Address, key: Uint8Array): Promise { + return ( + this.topStorageStack().get(`${address.toString()}_${bytesToHex(key)}`) ?? new Uint8Array(0) + ) + } + + async putContractStorage(address: Address, key: Uint8Array, value: Uint8Array): Promise { + this.topStorageStack().set(`${address.toString()}_${bytesToHex(key)}`, value) + } + + async checkpoint(): Promise { + this.checkpointSync() + } + async commit(): Promise { + this.accountStack.splice(-2, 1) + this.codeStack.splice(-2, 1) + this.storageStack.splice(-2, 1) + } + + async revert(): Promise { + this.accountStack.pop() + this.codeStack.pop() + this.storageStack.pop() + } + + async flush(): Promise {} + clearCaches(): void {} + + shallowCopy(): EVMStateManagerInterface { + 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 { + throw new Error('Method not implemented.') + } + setStateRoot(): Promise { + throw new Error('Method not implemented.') + } + hasStateRoot(): Promise { + throw new Error('Method not implemented.') + } + + // Only goes for long term create situations, skip + async clearContractStorage(): Promise {} + + // Only "core" methods implemented + checkChunkWitnessPresent?(): Promise { + throw new Error('Method not implemented.') + } + dumpStorage(): Promise { + throw new Error('Method not implemented.') + } + dumpStorageRange(): Promise { + throw new Error('Method not implemented.') + } + generateCanonicalGenesis(): Promise { + throw new Error('Method not implemented.') + } + getProof(): Promise { + throw new Error('Method not implemented.') + } + getAppliedKey?(): Uint8Array { + throw new Error('Method not implemented.') + } +} diff --git a/packages/statemanager/src/stateManager.ts b/packages/statemanager/src/stateManager.ts index 9c08670e2e..24769b8320 100644 --- a/packages/statemanager/src/stateManager.ts +++ b/packages/statemanager/src/stateManager.ts @@ -25,8 +25,13 @@ import { import debugDefault from 'debug' import { keccak256 } from 'ethereum-cryptography/keccak.js' -import { AccountCache, CacheType, CodeCache, StorageCache } from './cache/index.js' -import { OriginalStorageCache } from './cache/originalStorageCache.js' +import { + AccountCache, + CacheType, + CodeCache, + OriginalStorageCache, + StorageCache, +} from './cache/index.js' import type { AccountFields, @@ -155,6 +160,11 @@ export interface DefaultStateManagerOpts { * * The default state manager implementation uses a * `@ethereumjs/trie` trie as a data backend. + * + * Note that there is a `SimpleStateManager` dependency-free state + * manager implementation available shipped with the `@ethereumjs/statemanager` + * package which might be an alternative to this implementation + * for many basic use cases. */ export class DefaultStateManager implements EVMStateManagerInterface { protected _debug: Debugger diff --git a/packages/statemanager/src/statelessVerkleStateManager.ts b/packages/statemanager/src/statelessVerkleStateManager.ts index fb27cf144a..877a4fd273 100644 --- a/packages/statemanager/src/statelessVerkleStateManager.ts +++ b/packages/statemanager/src/statelessVerkleStateManager.ts @@ -23,8 +23,13 @@ import debugDefault from 'debug' import { keccak256 } from 'ethereum-cryptography/keccak.js' import { AccessWitness, AccessedStateType, decodeValue } from './accessWitness.js' -import { AccountCache, CacheType, CodeCache, StorageCache } from './cache/index.js' -import { OriginalStorageCache } from './cache/originalStorageCache.js' +import { + AccountCache, + CacheType, + CodeCache, + OriginalStorageCache, + StorageCache, +} from './cache/index.js' import type { AccessedStateWithAddress } from './accessWitness.js' import type { DefaultStateManager } from './stateManager.js' diff --git a/packages/statemanager/test/checkpointing.account.spec.ts b/packages/statemanager/test/checkpointing.account.spec.ts index 808466a3a2..95074d2d59 100644 --- a/packages/statemanager/test/checkpointing.account.spec.ts +++ b/packages/statemanager/test/checkpointing.account.spec.ts @@ -1,13 +1,15 @@ import { Account, Address, hexToBytes } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' -import { DefaultStateManager } from '../src/index.js' +import { DefaultStateManager, SimpleStateManager } from '../src/index.js' + +import type { StateManagerInterface } from '@ethereumjs/common' /** * Compares account read to none or undefined */ const accountEval = async ( - sm: DefaultStateManager, + sm: StateManagerInterface, address: Address, compare: bigint | undefined ) => { @@ -28,6 +30,8 @@ type CompareList = [Account | undefined, bigint | undefined] describe('StateManager -> Account Checkpointing', () => { const address = new Address(hexToBytes(`0x${'11'.repeat(20)}`)) + const stateManagers = [DefaultStateManager, SimpleStateManager] + const accountN1: CompareList = [ Account.fromAccountData({ nonce: 1, @@ -112,245 +116,247 @@ describe('StateManager -> Account Checkpointing', () => { }, ] - for (const as of accountSets) { - it(`No CP -> A1 -> Flush() (-> A1)`, async () => { - const sm = new DefaultStateManager() - - await sm.putAccount(address, as.a1[0]) - await sm.flush() - assert.ok(await accountEval(sm, address, as.a1[1])) - - sm.clearCaches() - assert.ok(await accountEval(sm, address, as.a1[1])) - }) - - it(`CP -> A1.1 -> Commit -> Flush() (-> A1.1)`, async () => { - const sm = new DefaultStateManager() - - await sm.checkpoint() - await sm.putAccount(address, as.a1[0]) - await sm.commit() - await sm.flush() - assert.ok(await accountEval(sm, address, as.a1[1])) - - sm.clearCaches() - assert.ok(await accountEval(sm, address, as.a1[1])) - }) - - it(`CP -> A1.1 -> Revert -> Flush() (-> Undefined)`, async () => { - const sm = new DefaultStateManager() - - await sm.checkpoint() - await sm.putAccount(address, as.a1[0]) - await sm.revert() - await sm.flush() - assert.ok(await accountEval(sm, address, undefined)) - - sm.clearCaches() - assert.ok(await accountEval(sm, address, undefined)) - }) - - it(`A1.1 -> CP -> Commit -> Flush() (-> A1.1)`, async () => { - const sm = new DefaultStateManager() - - await sm.putAccount(address, as.a1[0]) - await sm.checkpoint() - await sm.commit() - await sm.flush() - assert.ok(await accountEval(sm, address, as.a1[1])) - - sm.clearCaches() - assert.ok(await accountEval(sm, address, as.a1[1])) - }) - - it(`A1.1 -> CP -> Revert -> Flush() (-> A1.1)`, async () => { - const sm = new DefaultStateManager() - - await sm.putAccount(address, as.a1[0]) - await sm.checkpoint() - await sm.revert() - await sm.flush() - assert.ok(await accountEval(sm, address, as.a1[1])) - - sm.clearCaches() - assert.ok(await accountEval(sm, address, as.a1[1])) - }) - - it(`A1.1 -> CP -> A1.2 -> Commit -> Flush() (-> A1.2)`, async () => { - const sm = new DefaultStateManager() - - await sm.putAccount(address, as.a1[0]) - await sm.checkpoint() - await sm.putAccount(address, as.a2[0]) - await sm.commit() - await sm.flush() - assert.ok(await accountEval(sm, address, as.a2[1])) - - sm.clearCaches() - assert.ok(await accountEval(sm, address, as.a2[1])) - }) - - it(`A1.1 -> CP -> A1.2 -> Commit -> A1.3 -> Flush() (-> A1.3)`, async () => { - const sm = new DefaultStateManager() - - await sm.putAccount(address, as.a1[0]) - await sm.checkpoint() - await sm.putAccount(address, as.a2[0]) - await sm.commit() - await sm.putAccount(address, as.a3[0]) - await sm.flush() - assert.ok(await accountEval(sm, address, as.a3[1])) - - sm.clearCaches() - assert.ok(await accountEval(sm, address, as.a3[1])) - }) - - it(`A1.1 -> CP -> A1.2 -> A1.3 -> Commit -> Flush() (-> A1.3)`, async () => { - const sm = new DefaultStateManager() - - await sm.putAccount(address, as.a1[0]) - await sm.checkpoint() - await sm.putAccount(address, as.a2[0]) - await sm.putAccount(address, as.a3[0]) - await sm.commit() - await sm.flush() - assert.ok(await accountEval(sm, address, as.a3[1])) - - sm.clearCaches() - assert.ok(await accountEval(sm, address, as.a3[1])) - }) - - it(`CP -> A1.1 -> A1.2 -> Commit -> Flush() (-> A1.2)`, async () => { - const sm = new DefaultStateManager() - - await sm.checkpoint() - await sm.putAccount(address, as.a1[0]) - await sm.putAccount(address, as.a2[0]) - await sm.commit() - await sm.flush() - assert.ok(await accountEval(sm, address, as.a2[1])) - - sm.clearCaches() - assert.ok(await accountEval(sm, address, as.a2[1])) - }) - - it(`CP -> A1.1 -> A1.2 -> Revert -> Flush() (-> Undefined)`, async () => { - const sm = new DefaultStateManager() - - await sm.checkpoint() - await sm.putAccount(address, as.a1[0]) - - await sm.putAccount(address, as.a2[0]) - await sm.revert() - await sm.flush() - assert.ok(await accountEval(sm, address, undefined)) - - sm.clearCaches() - assert.ok(await accountEval(sm, address, undefined)) - }) - - it(`A1.1 -> CP -> A1.2 -> Revert -> Flush() (-> A1.1)`, async () => { - const sm = new DefaultStateManager() - - await sm.putAccount(address, as.a1[0]) - await sm.checkpoint() - await sm.putAccount(address, as.a2[0]) - await sm.revert() - await sm.flush() - assert.ok(await accountEval(sm, address, as.a1[1])) - - sm.clearCaches() - assert.ok(await accountEval(sm, address, as.a1[1])) - }) - - it('A1.1 -> CP -> A1.2 -> CP -> A1.3 -> Commit -> Commit -> Flush() (-> A1.3)', async () => { - const sm = new DefaultStateManager() - - await sm.putAccount(address, as.a1[0]) - await sm.checkpoint() - await sm.putAccount(address, as.a2[0]) - await sm.checkpoint() - await sm.putAccount(address, as.a3[0]) - await sm.commit() - await sm.commit() - await sm.flush() - assert.ok(await accountEval(sm, address, as.a3[1])) - - sm.clearCaches() - assert.ok(await accountEval(sm, address, as.a3[1])) - }) - - it('A1.1 -> CP -> A1.2 -> CP -> A1.3 -> Commit -> Revert -> Flush() (-> A1.1)', async () => { - const sm = new DefaultStateManager() - - await sm.putAccount(address, as.a1[0]) - await sm.checkpoint() - await sm.putAccount(address, as.a2[0]) - await sm.checkpoint() - await sm.putAccount(address, as.a3[0]) - await sm.commit() - await sm.revert() - await sm.flush() - assert.ok(await accountEval(sm, address, as.a1[1])) - - sm.clearCaches() - assert.ok(await accountEval(sm, address, as.a1[1])) - }) - - it('A1.1 -> CP -> A1.2 -> CP -> A1.3 -> Revert -> Commit -> Flush() (-> A1.2)', async () => { - const sm = new DefaultStateManager() - - await sm.putAccount(address, as.a1[0]) - await sm.checkpoint() - await sm.putAccount(address, as.a2[0]) - await sm.checkpoint() - await sm.putAccount(address, as.a3[0]) - await sm.revert() - await sm.commit() - await sm.flush() - assert.ok(await accountEval(sm, address, as.a2[1])) - - sm.clearCaches() - assert.ok(await accountEval(sm, address, as.a2[1])) - }) - - it('A1.1 -> CP -> A1.2 -> CP -> A1.3 -> Revert -> A1.4 -> Commit -> Flush() (-> A1.4)', async () => { - const sm = new DefaultStateManager() - - await sm.putAccount(address, as.a1[0]) - await sm.checkpoint() - await sm.putAccount(address, as.a2[0]) - await sm.checkpoint() - await sm.putAccount(address, as.a3[0]) - await sm.revert() - await sm.putAccount(address, as.a4[0]) - await sm.commit() - await sm.flush() - assert.ok(await accountEval(sm, address, as.a4[1])) - - sm.clearCaches() - assert.ok(await accountEval(sm, address, as.a4[1])) - }) - - it('A1.1 -> CP -> A1.2 -> CP -> A1.3 -> Revert -> A1.4 -> CP -> A1.5 -> Commit -> Commit -> Flush() (-> A1.5)', async () => { - const sm = new DefaultStateManager() - - await sm.putAccount(address, as.a1[0]) - await sm.checkpoint() - await sm.putAccount(address, as.a2[0]) - await sm.checkpoint() - await sm.putAccount(address, as.a3[0]) - await sm.revert() - await sm.putAccount(address, as.a4[0]) - await sm.checkpoint() - await sm.putAccount(address, as.a5[0]) - await sm.commit() - await sm.commit() - await sm.flush() - assert.ok(await accountEval(sm, address, as.a5[1])) - - sm.clearCaches() - assert.ok(await accountEval(sm, address, as.a5[1])) - }) + for (const SM of stateManagers) { + for (const as of accountSets) { + it(`No CP -> A1 -> Flush() (-> A1)`, async () => { + const sm = new SM() + + await sm.putAccount(address, as.a1[0]) + await sm.flush() + assert.ok(await accountEval(sm, address, as.a1[1])) + + sm.clearCaches() + assert.ok(await accountEval(sm, address, as.a1[1])) + }) + + it(`CP -> A1.1 -> Commit -> Flush() (-> A1.1)`, async () => { + const sm = new SM() + + await sm.checkpoint() + await sm.putAccount(address, as.a1[0]) + await sm.commit() + await sm.flush() + assert.ok(await accountEval(sm, address, as.a1[1])) + + sm.clearCaches() + assert.ok(await accountEval(sm, address, as.a1[1])) + }) + + it(`CP -> A1.1 -> Revert -> Flush() (-> Undefined)`, async () => { + const sm = new SM() + + await sm.checkpoint() + await sm.putAccount(address, as.a1[0]) + await sm.revert() + await sm.flush() + assert.ok(await accountEval(sm, address, undefined)) + + sm.clearCaches() + assert.ok(await accountEval(sm, address, undefined)) + }) + + it(`A1.1 -> CP -> Commit -> Flush() (-> A1.1)`, async () => { + const sm = new SM() + + await sm.putAccount(address, as.a1[0]) + await sm.checkpoint() + await sm.commit() + await sm.flush() + assert.ok(await accountEval(sm, address, as.a1[1])) + + sm.clearCaches() + assert.ok(await accountEval(sm, address, as.a1[1])) + }) + + it(`A1.1 -> CP -> Revert -> Flush() (-> A1.1)`, async () => { + const sm = new SM() + + await sm.putAccount(address, as.a1[0]) + await sm.checkpoint() + await sm.revert() + await sm.flush() + assert.ok(await accountEval(sm, address, as.a1[1])) + + sm.clearCaches() + assert.ok(await accountEval(sm, address, as.a1[1])) + }) + + it(`A1.1 -> CP -> A1.2 -> Commit -> Flush() (-> A1.2)`, async () => { + const sm = new SM() + + await sm.putAccount(address, as.a1[0]) + await sm.checkpoint() + await sm.putAccount(address, as.a2[0]) + await sm.commit() + await sm.flush() + assert.ok(await accountEval(sm, address, as.a2[1])) + + sm.clearCaches() + assert.ok(await accountEval(sm, address, as.a2[1])) + }) + + it(`A1.1 -> CP -> A1.2 -> Commit -> A1.3 -> Flush() (-> A1.3)`, async () => { + const sm = new SM() + + await sm.putAccount(address, as.a1[0]) + await sm.checkpoint() + await sm.putAccount(address, as.a2[0]) + await sm.commit() + await sm.putAccount(address, as.a3[0]) + await sm.flush() + assert.ok(await accountEval(sm, address, as.a3[1])) + + sm.clearCaches() + assert.ok(await accountEval(sm, address, as.a3[1])) + }) + + it(`A1.1 -> CP -> A1.2 -> A1.3 -> Commit -> Flush() (-> A1.3)`, async () => { + const sm = new SM() + + await sm.putAccount(address, as.a1[0]) + await sm.checkpoint() + await sm.putAccount(address, as.a2[0]) + await sm.putAccount(address, as.a3[0]) + await sm.commit() + await sm.flush() + assert.ok(await accountEval(sm, address, as.a3[1])) + + sm.clearCaches() + assert.ok(await accountEval(sm, address, as.a3[1])) + }) + + it(`CP -> A1.1 -> A1.2 -> Commit -> Flush() (-> A1.2)`, async () => { + const sm = new SM() + + await sm.checkpoint() + await sm.putAccount(address, as.a1[0]) + await sm.putAccount(address, as.a2[0]) + await sm.commit() + await sm.flush() + assert.ok(await accountEval(sm, address, as.a2[1])) + + sm.clearCaches() + assert.ok(await accountEval(sm, address, as.a2[1])) + }) + + it(`CP -> A1.1 -> A1.2 -> Revert -> Flush() (-> Undefined)`, async () => { + const sm = new SM() + + await sm.checkpoint() + await sm.putAccount(address, as.a1[0]) + + await sm.putAccount(address, as.a2[0]) + await sm.revert() + await sm.flush() + assert.ok(await accountEval(sm, address, undefined)) + + sm.clearCaches() + assert.ok(await accountEval(sm, address, undefined)) + }) + + it(`A1.1 -> CP -> A1.2 -> Revert -> Flush() (-> A1.1)`, async () => { + const sm = new SM() + + await sm.putAccount(address, as.a1[0]) + await sm.checkpoint() + await sm.putAccount(address, as.a2[0]) + await sm.revert() + await sm.flush() + assert.ok(await accountEval(sm, address, as.a1[1])) + + sm.clearCaches() + assert.ok(await accountEval(sm, address, as.a1[1])) + }) + + it('A1.1 -> CP -> A1.2 -> CP -> A1.3 -> Commit -> Commit -> Flush() (-> A1.3)', async () => { + const sm = new SM() + + await sm.putAccount(address, as.a1[0]) + await sm.checkpoint() + await sm.putAccount(address, as.a2[0]) + await sm.checkpoint() + await sm.putAccount(address, as.a3[0]) + await sm.commit() + await sm.commit() + await sm.flush() + assert.ok(await accountEval(sm, address, as.a3[1])) + + sm.clearCaches() + assert.ok(await accountEval(sm, address, as.a3[1])) + }) + + it('A1.1 -> CP -> A1.2 -> CP -> A1.3 -> Commit -> Revert -> Flush() (-> A1.1)', async () => { + const sm = new SM() + + await sm.putAccount(address, as.a1[0]) + await sm.checkpoint() + await sm.putAccount(address, as.a2[0]) + await sm.checkpoint() + await sm.putAccount(address, as.a3[0]) + await sm.commit() + await sm.revert() + await sm.flush() + assert.ok(await accountEval(sm, address, as.a1[1])) + + sm.clearCaches() + assert.ok(await accountEval(sm, address, as.a1[1])) + }) + + it('A1.1 -> CP -> A1.2 -> CP -> A1.3 -> Revert -> Commit -> Flush() (-> A1.2)', async () => { + const sm = new SM() + + await sm.putAccount(address, as.a1[0]) + await sm.checkpoint() + await sm.putAccount(address, as.a2[0]) + await sm.checkpoint() + await sm.putAccount(address, as.a3[0]) + await sm.revert() + await sm.commit() + await sm.flush() + assert.ok(await accountEval(sm, address, as.a2[1])) + + sm.clearCaches() + assert.ok(await accountEval(sm, address, as.a2[1])) + }) + + it('A1.1 -> CP -> A1.2 -> CP -> A1.3 -> Revert -> A1.4 -> Commit -> Flush() (-> A1.4)', async () => { + const sm = new SM() + + await sm.putAccount(address, as.a1[0]) + await sm.checkpoint() + await sm.putAccount(address, as.a2[0]) + await sm.checkpoint() + await sm.putAccount(address, as.a3[0]) + await sm.revert() + await sm.putAccount(address, as.a4[0]) + await sm.commit() + await sm.flush() + assert.ok(await accountEval(sm, address, as.a4[1])) + + sm.clearCaches() + assert.ok(await accountEval(sm, address, as.a4[1])) + }) + + it('A1.1 -> CP -> A1.2 -> CP -> A1.3 -> Revert -> A1.4 -> CP -> A1.5 -> Commit -> Commit -> Flush() (-> A1.5)', async () => { + const sm = new SM() + + await sm.putAccount(address, as.a1[0]) + await sm.checkpoint() + await sm.putAccount(address, as.a2[0]) + await sm.checkpoint() + await sm.putAccount(address, as.a3[0]) + await sm.revert() + await sm.putAccount(address, as.a4[0]) + await sm.checkpoint() + await sm.putAccount(address, as.a5[0]) + await sm.commit() + await sm.commit() + await sm.flush() + assert.ok(await accountEval(sm, address, as.a5[1])) + + sm.clearCaches() + assert.ok(await accountEval(sm, address, as.a5[1])) + }) + } } }) diff --git a/packages/statemanager/test/checkpointing.code.spec.ts b/packages/statemanager/test/checkpointing.code.spec.ts index dd452dadf6..673451c1c3 100644 --- a/packages/statemanager/test/checkpointing.code.spec.ts +++ b/packages/statemanager/test/checkpointing.code.spec.ts @@ -1,10 +1,11 @@ +import { type StateManagerInterface } from '@ethereumjs/common' import { Account, Address, hexToBytes } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' -import { DefaultStateManager } from '../src/index.js' +import { DefaultStateManager, SimpleStateManager } from '../src/index.js' const codeEval = async ( - sm: DefaultStateManager, + sm: StateManagerInterface, address: Address, value: Uint8Array, root: Uint8Array @@ -16,7 +17,8 @@ const codeEval = async ( describe('StateManager -> Code Checkpointing', () => { const address = new Address(hexToBytes(`0x${'11'.repeat(20)}`)) - const account = new Account() + + const stateManagers = [DefaultStateManager, SimpleStateManager] const value = hexToBytes('0x01') const root = hexToBytes('0x5fe7f977e71dba2ea1a68e21057beebb9be2ac30c6410aa38d4f3fbe41dcffd2') @@ -88,265 +90,267 @@ describe('StateManager -> Code Checkpointing', () => { }, ] - for (const c of codeSets) { - it(`No CP -> C1 -> Flush() (-> C1)`, async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.putContractCode(address, c.c1.value) - - await sm.flush() - await codeEval(sm, address, c.c1.value, c.c1.root) - - sm.clearCaches() - assert.deepEqual(await sm.getContractCode(address), c.c1.value) - await codeEval(sm, address, c.c1.value, c.c1.root) - }) - - it(`CP -> C1.1 -> Commit -> Flush() (-> C1.1)`, async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.checkpoint() - await sm.putContractCode(address, c.c1.value) - await sm.commit() - await sm.flush() - await codeEval(sm, address, c.c1.value, c.c1.root) - - sm.clearCaches() - await codeEval(sm, address, c.c1.value, c.c1.root) - }) - - it(`CP -> C1.1 -> Revert -> Flush() (-> Undefined)`, async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.checkpoint() - await sm.putContractCode(address, c.c1.value) - - await sm.revert() - await sm.flush() - await codeEval(sm, address, valueEmpty, rootEmpty) - - sm.clearCaches() - - await codeEval(sm, address, valueEmpty, rootEmpty) - }) - - it(`C1.1 -> CP -> Commit -> Flush() (-> C1.1)`, async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.putContractCode(address, c.c1.value) - await sm.checkpoint() - await sm.commit() - await sm.flush() - await codeEval(sm, address, c.c1.value, c.c1.root) - - sm.clearCaches() - await codeEval(sm, address, c.c1.value, c.c1.root) - }) - - it(`C1.1 -> CP -> Revert -> Flush() (-> C1.1)`, async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.putContractCode(address, c.c1.value) - await sm.checkpoint() - await sm.revert() - await sm.flush() - await codeEval(sm, address, c.c1.value, c.c1.root) - - sm.clearCaches() - await codeEval(sm, address, c.c1.value, c.c1.root) - }) - - it(`C1.1 -> CP -> C1.2 -> Commit -> Flush() (-> C1.2)`, async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.putContractCode(address, c.c1.value) - await sm.checkpoint() - await sm.putContractCode(address, c.c2.value) - await sm.commit() - await sm.flush() - await codeEval(sm, address, c.c2.value, c.c2.root) - - sm.clearCaches() - await codeEval(sm, address, c.c2.value, c.c2.root) - }) - - it(`C1.1 -> CP -> C1.2 -> Commit -> C1.3 -> Flush() (-> C1.3)`, async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.putContractCode(address, c.c1.value) - await sm.checkpoint() - await sm.putContractCode(address, c.c2.value) - await sm.commit() - await sm.putContractCode(address, c.c3.value) - await sm.flush() - await codeEval(sm, address, c.c3.value, c.c3.root) - - sm.clearCaches() - await codeEval(sm, address, c.c3.value, c.c3.root) - }) - - it(`C1.1 -> CP -> C1.2 -> C1.3 -> Commit -> Flush() (-> C1.3)`, async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.putContractCode(address, c.c1.value) - await sm.checkpoint() - await sm.putContractCode(address, c.c2.value) - await sm.putContractCode(address, c.c3.value) - await sm.commit() - await sm.flush() - await codeEval(sm, address, c.c3.value, c.c3.root) - - sm.clearCaches() - await codeEval(sm, address, c.c3.value, c.c3.root) - }) - - it(`CP -> C1.1 -> C1.2 -> Commit -> Flush() (-> C1.2)`, async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.checkpoint() - await sm.putContractCode(address, c.c1.value) - await sm.putContractCode(address, c.c2.value) - await sm.commit() - await sm.flush() - await codeEval(sm, address, c.c2.value, c.c2.root) - - sm.clearCaches() - await codeEval(sm, address, c.c2.value, c.c2.root) - }) - - it(`CP -> C1.1 -> C1.2 -> Revert -> Flush() (-> Undefined)`, async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.checkpoint() - await sm.putContractCode(address, c.c1.value) - - await sm.putContractCode(address, c.c2.value) - await sm.revert() - await sm.flush() - await codeEval(sm, address, valueEmpty, rootEmpty) - - sm.clearCaches() - await codeEval(sm, address, valueEmpty, rootEmpty) - }) - - it(`C1.1 -> CP -> C1.2 -> Revert -> Flush() (-> C1.1)`, async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.putContractCode(address, c.c1.value) - await sm.checkpoint() - await sm.putContractCode(address, c.c2.value) - await sm.revert() - await sm.flush() - await codeEval(sm, address, c.c1.value, c.c1.root) - - sm.clearCaches() - await codeEval(sm, address, c.c1.value, c.c1.root) - }) - - it('C1.1 -> CP -> C1.2 -> CP -> C1.3 -> Commit -> Commit -> Flush() (-> C1.3)', async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.putContractCode(address, c.c1.value) - await sm.checkpoint() - await sm.putContractCode(address, c.c2.value) - await sm.checkpoint() - await sm.putContractCode(address, c.c3.value) - await sm.commit() - await sm.commit() - await sm.flush() - await codeEval(sm, address, c.c3.value, c.c3.root) - - sm.clearCaches() - await codeEval(sm, address, c.c3.value, c.c3.root) - }) - - it('C1.1 -> CP -> C1.2 -> CP -> C1.3 -> Commit -> Revert -> Flush() (-> C1.1)', async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.putContractCode(address, c.c1.value) - await sm.checkpoint() - await sm.putContractCode(address, c.c2.value) - await sm.checkpoint() - await sm.putContractCode(address, c.c3.value) - await sm.commit() - await sm.revert() - await sm.flush() - await codeEval(sm, address, c.c1.value, c.c1.root) - - sm.clearCaches() - await codeEval(sm, address, c.c1.value, c.c1.root) - }) - - it('C1.1 -> CP -> C1.2 -> CP -> C1.3 -> Revert -> Commit -> Flush() (-> C1.2)', async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.putContractCode(address, c.c1.value) - await sm.checkpoint() - await sm.putContractCode(address, c.c2.value) - await sm.checkpoint() - await sm.putContractCode(address, c.c3.value) - await sm.revert() - await sm.commit() - await sm.flush() - await codeEval(sm, address, c.c2.value, c.c2.root) - - sm.clearCaches() - await codeEval(sm, address, c.c2.value, c.c2.root) - }) - - it('C1.1 -> CP -> C1.2 -> CP -> C1.3 -> Revert -> C1.4 -> Commit -> Flush() (-> C1.4)', async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.putContractCode(address, c.c1.value) - await sm.checkpoint() - await sm.putContractCode(address, c.c2.value) - await sm.checkpoint() - await sm.putContractCode(address, c.c3.value) - await sm.revert() - await sm.putContractCode(address, c.c4.value) - await sm.commit() - await sm.flush() - await codeEval(sm, address, c.c4.value, c.c4.root) - - sm.clearCaches() - await codeEval(sm, address, c.c4.value, c.c4.root) - }) - - it('C1.1 -> CP -> C1.2 -> CP -> C1.3 -> Revert -> C1.4 -> CP -> C1.5 -> Commit -> Commit -> Flush() (-> C1.5)', async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.putContractCode(address, c.c1.value) - await sm.checkpoint() - await sm.putContractCode(address, c.c2.value) - await sm.checkpoint() - await sm.putContractCode(address, c.c3.value) - await sm.revert() - await sm.putContractCode(address, c.c4.value) - await sm.checkpoint() - await sm.putContractCode(address, c.c5.value) - await sm.commit() - await sm.commit() - await sm.flush() - await codeEval(sm, address, c.c5.value, c.c5.root) - - sm.clearCaches() - await codeEval(sm, address, c.c5.value, c.c5.root) - }) + for (const SM of stateManagers) { + for (const c of codeSets) { + it(`No CP -> C1 -> Flush() (-> C1)`, async () => { + const sm = new SM() + await sm.putAccount(address, new Account()) + + await sm.putContractCode(address, c.c1.value) + + await sm.flush() + await codeEval(sm, address, c.c1.value, c.c1.root) + + sm.clearCaches() + assert.deepEqual(await sm.getContractCode(address), c.c1.value) + await codeEval(sm, address, c.c1.value, c.c1.root) + }) + + it(`CP -> C1.1 -> Commit -> Flush() (-> C1.1)`, async () => { + const sm = new SM() + await sm.putAccount(address, new Account()) + + await sm.checkpoint() + await sm.putContractCode(address, c.c1.value) + await sm.commit() + await sm.flush() + await codeEval(sm, address, c.c1.value, c.c1.root) + + sm.clearCaches() + await codeEval(sm, address, c.c1.value, c.c1.root) + }) + + it(`CP -> C1.1 -> Revert -> Flush() (-> Undefined)`, async () => { + const sm = new SM() + await sm.putAccount(address, new Account()) + + await sm.checkpoint() + await sm.putContractCode(address, c.c1.value) + + await sm.revert() + await sm.flush() + await codeEval(sm, address, valueEmpty, rootEmpty) + + sm.clearCaches() + + await codeEval(sm, address, valueEmpty, rootEmpty) + }) + + it(`C1.1 -> CP -> Commit -> Flush() (-> C1.1)`, async () => { + const sm = new SM() + await sm.putAccount(address, new Account()) + + await sm.putContractCode(address, c.c1.value) + await sm.checkpoint() + await sm.commit() + await sm.flush() + await codeEval(sm, address, c.c1.value, c.c1.root) + + sm.clearCaches() + await codeEval(sm, address, c.c1.value, c.c1.root) + }) + + it(`C1.1 -> CP -> Revert -> Flush() (-> C1.1)`, async () => { + const sm = new SM() + await sm.putAccount(address, new Account()) + + await sm.putContractCode(address, c.c1.value) + await sm.checkpoint() + await sm.revert() + await sm.flush() + await codeEval(sm, address, c.c1.value, c.c1.root) + + sm.clearCaches() + await codeEval(sm, address, c.c1.value, c.c1.root) + }) + + it(`C1.1 -> CP -> C1.2 -> Commit -> Flush() (-> C1.2)`, async () => { + const sm = new SM() + await sm.putAccount(address, new Account()) + + await sm.putContractCode(address, c.c1.value) + await sm.checkpoint() + await sm.putContractCode(address, c.c2.value) + await sm.commit() + await sm.flush() + await codeEval(sm, address, c.c2.value, c.c2.root) + + sm.clearCaches() + await codeEval(sm, address, c.c2.value, c.c2.root) + }) + + it(`C1.1 -> CP -> C1.2 -> Commit -> C1.3 -> Flush() (-> C1.3)`, async () => { + const sm = new SM() + await sm.putAccount(address, new Account()) + + await sm.putContractCode(address, c.c1.value) + await sm.checkpoint() + await sm.putContractCode(address, c.c2.value) + await sm.commit() + await sm.putContractCode(address, c.c3.value) + await sm.flush() + await codeEval(sm, address, c.c3.value, c.c3.root) + + sm.clearCaches() + await codeEval(sm, address, c.c3.value, c.c3.root) + }) + + it(`C1.1 -> CP -> C1.2 -> C1.3 -> Commit -> Flush() (-> C1.3)`, async () => { + const sm = new SM() + await sm.putAccount(address, new Account()) + + await sm.putContractCode(address, c.c1.value) + await sm.checkpoint() + await sm.putContractCode(address, c.c2.value) + await sm.putContractCode(address, c.c3.value) + await sm.commit() + await sm.flush() + await codeEval(sm, address, c.c3.value, c.c3.root) + + sm.clearCaches() + await codeEval(sm, address, c.c3.value, c.c3.root) + }) + + it(`CP -> C1.1 -> C1.2 -> Commit -> Flush() (-> C1.2)`, async () => { + const sm = new SM() + await sm.putAccount(address, new Account()) + + await sm.checkpoint() + await sm.putContractCode(address, c.c1.value) + await sm.putContractCode(address, c.c2.value) + await sm.commit() + await sm.flush() + await codeEval(sm, address, c.c2.value, c.c2.root) + + sm.clearCaches() + await codeEval(sm, address, c.c2.value, c.c2.root) + }) + + it(`CP -> C1.1 -> C1.2 -> Revert -> Flush() (-> Undefined)`, async () => { + const sm = new SM() + await sm.putAccount(address, new Account()) + + await sm.checkpoint() + await sm.putContractCode(address, c.c1.value) + + await sm.putContractCode(address, c.c2.value) + await sm.revert() + await sm.flush() + await codeEval(sm, address, valueEmpty, rootEmpty) + + sm.clearCaches() + await codeEval(sm, address, valueEmpty, rootEmpty) + }) + + it(`C1.1 -> CP -> C1.2 -> Revert -> Flush() (-> C1.1)`, async () => { + const sm = new SM() + await sm.putAccount(address, new Account()) + + await sm.putContractCode(address, c.c1.value) + await sm.checkpoint() + await sm.putContractCode(address, c.c2.value) + await sm.revert() + await sm.flush() + await codeEval(sm, address, c.c1.value, c.c1.root) + + sm.clearCaches() + await codeEval(sm, address, c.c1.value, c.c1.root) + }) + + it('C1.1 -> CP -> C1.2 -> CP -> C1.3 -> Commit -> Commit -> Flush() (-> C1.3)', async () => { + const sm = new SM() + await sm.putAccount(address, new Account()) + + await sm.putContractCode(address, c.c1.value) + await sm.checkpoint() + await sm.putContractCode(address, c.c2.value) + await sm.checkpoint() + await sm.putContractCode(address, c.c3.value) + await sm.commit() + await sm.commit() + await sm.flush() + await codeEval(sm, address, c.c3.value, c.c3.root) + + sm.clearCaches() + await codeEval(sm, address, c.c3.value, c.c3.root) + }) + + it('C1.1 -> CP -> C1.2 -> CP -> C1.3 -> Commit -> Revert -> Flush() (-> C1.1)', async () => { + const sm = new SM() + await sm.putAccount(address, new Account()) + + await sm.putContractCode(address, c.c1.value) + await sm.checkpoint() + await sm.putContractCode(address, c.c2.value) + await sm.checkpoint() + await sm.putContractCode(address, c.c3.value) + await sm.commit() + await sm.revert() + await sm.flush() + await codeEval(sm, address, c.c1.value, c.c1.root) + + sm.clearCaches() + await codeEval(sm, address, c.c1.value, c.c1.root) + }) + + it('C1.1 -> CP -> C1.2 -> CP -> C1.3 -> Revert -> Commit -> Flush() (-> C1.2)', async () => { + const sm = new SM() + await sm.putAccount(address, new Account()) + + await sm.putContractCode(address, c.c1.value) + await sm.checkpoint() + await sm.putContractCode(address, c.c2.value) + await sm.checkpoint() + await sm.putContractCode(address, c.c3.value) + await sm.revert() + await sm.commit() + await sm.flush() + await codeEval(sm, address, c.c2.value, c.c2.root) + + sm.clearCaches() + await codeEval(sm, address, c.c2.value, c.c2.root) + }) + + it('C1.1 -> CP -> C1.2 -> CP -> C1.3 -> Revert -> C1.4 -> Commit -> Flush() (-> C1.4)', async () => { + const sm = new SM() + await sm.putAccount(address, new Account()) + + await sm.putContractCode(address, c.c1.value) + await sm.checkpoint() + await sm.putContractCode(address, c.c2.value) + await sm.checkpoint() + await sm.putContractCode(address, c.c3.value) + await sm.revert() + await sm.putContractCode(address, c.c4.value) + await sm.commit() + await sm.flush() + await codeEval(sm, address, c.c4.value, c.c4.root) + + sm.clearCaches() + await codeEval(sm, address, c.c4.value, c.c4.root) + }) + + it('C1.1 -> CP -> C1.2 -> CP -> C1.3 -> Revert -> C1.4 -> CP -> C1.5 -> Commit -> Commit -> Flush() (-> C1.5)', async () => { + const sm = new SM() + await sm.putAccount(address, new Account()) + + await sm.putContractCode(address, c.c1.value) + await sm.checkpoint() + await sm.putContractCode(address, c.c2.value) + await sm.checkpoint() + await sm.putContractCode(address, c.c3.value) + await sm.revert() + await sm.putContractCode(address, c.c4.value) + await sm.checkpoint() + await sm.putContractCode(address, c.c5.value) + await sm.commit() + await sm.commit() + await sm.flush() + await codeEval(sm, address, c.c5.value, c.c5.root) + + sm.clearCaches() + await codeEval(sm, address, c.c5.value, c.c5.root) + }) + } } }) diff --git a/packages/statemanager/test/checkpointing.storage.spec.ts b/packages/statemanager/test/checkpointing.storage.spec.ts index ca9afe8d35..0e6d5a2a1e 100644 --- a/packages/statemanager/test/checkpointing.storage.spec.ts +++ b/packages/statemanager/test/checkpointing.storage.spec.ts @@ -1,27 +1,42 @@ import { Account, Address, hexToBytes } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' -import { DefaultStateManager } from '../src/index.js' +import { DefaultStateManager, SimpleStateManager } from '../src/index.js' + +import type { StateManagerInterface } from '@ethereumjs/common' const storageEval = async ( - sm: DefaultStateManager, + sm: StateManagerInterface, address: Address, key: Uint8Array, value: Uint8Array, - root: Uint8Array + root: Uint8Array, + rootCheck = true ) => { assert.deepEqual( await sm.getContractStorage(address, key), value, 'storage value should be equal' ) - const accountCMP = await sm.getAccount(address) - assert.deepEqual(accountCMP!.storageRoot, root, 'account storage root should be equal') + if (rootCheck) { + const accountCMP = await sm.getAccount(address) + assert.deepEqual(accountCMP!.storageRoot, root, 'account storage root should be equal') + } } describe('StateManager -> Storage Checkpointing', () => { const address = new Address(hexToBytes(`0x${'11'.repeat(20)}`)) - const account = new Account() + + const stateManagers = [ + { + SM: DefaultStateManager, + rootCheck: true, + }, + { + SM: SimpleStateManager, + rootCheck: false, + }, + ] const key = hexToBytes(`0x${'01'.repeat(32)}`) @@ -95,264 +110,266 @@ describe('StateManager -> Storage Checkpointing', () => { }, ] - for (const s of storageSets) { - it(`No CP -> S1 -> Flush() (-> S1)`, async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.putContractStorage(address, key, s.s1.value) - await sm.flush() - await storageEval(sm, address, key, s.s1.value, s.s1.root) - - sm.clearCaches() - assert.deepEqual(await sm.getContractStorage(address, key), s.s1.value) - await storageEval(sm, address, key, s.s1.value, s.s1.root) - }) - - it(`CP -> S1.1 -> Commit -> Flush() (-> S1.1)`, async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.checkpoint() - await sm.putContractStorage(address, key, s.s1.value) - await sm.commit() - await sm.flush() - await storageEval(sm, address, key, s.s1.value, s.s1.root) - - sm.clearCaches() - await storageEval(sm, address, key, s.s1.value, s.s1.root) - }) - - it(`CP -> S1.1 -> Revert -> Flush() (-> Undefined)`, async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.checkpoint() - await sm.putContractStorage(address, key, s.s1.value) - - await sm.revert() - await sm.flush() - await storageEval(sm, address, key, valueEmpty, rootEmpty) - - sm.clearCaches() - - await storageEval(sm, address, key, valueEmpty, rootEmpty) - }) - - it(`S1.1 -> CP -> Commit -> Flush() (-> S1.1)`, async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.putContractStorage(address, key, s.s1.value) - await sm.checkpoint() - await sm.commit() - await sm.flush() - await storageEval(sm, address, key, s.s1.value, s.s1.root) - - sm.clearCaches() - await storageEval(sm, address, key, s.s1.value, s.s1.root) - }) - - it(`S1.1 -> CP -> Revert -> Flush() (-> S1.1)`, async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.putContractStorage(address, key, s.s1.value) - await sm.checkpoint() - await sm.revert() - await sm.flush() - await storageEval(sm, address, key, s.s1.value, s.s1.root) - - sm.clearCaches() - await storageEval(sm, address, key, s.s1.value, s.s1.root) - }) - - it(`S1.1 -> CP -> S1.2 -> Commit -> Flush() (-> S1.2)`, async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.putContractStorage(address, key, s.s1.value) - await sm.checkpoint() - await sm.putContractStorage(address, key, s.s2.value) - await sm.commit() - await sm.flush() - await storageEval(sm, address, key, s.s2.value, s.s2.root) - - sm.clearCaches() - await storageEval(sm, address, key, s.s2.value, s.s2.root) - }) - - it(`S1.1 -> CP -> S1.2 -> Commit -> S1.3 -> Flush() (-> S1.3)`, async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.putContractStorage(address, key, s.s1.value) - await sm.checkpoint() - await sm.putContractStorage(address, key, s.s2.value) - await sm.commit() - await sm.putContractStorage(address, key, s.s3.value) - await sm.flush() - await storageEval(sm, address, key, s.s3.value, s.s3.root) - - sm.clearCaches() - await storageEval(sm, address, key, s.s3.value, s.s3.root) - }) - - it(`S1.1 -> CP -> S1.2 -> S1.3 -> Commit -> Flush() (-> S1.3)`, async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.putContractStorage(address, key, s.s1.value) - await sm.checkpoint() - await sm.putContractStorage(address, key, s.s2.value) - await sm.putContractStorage(address, key, s.s3.value) - await sm.commit() - await sm.flush() - await storageEval(sm, address, key, s.s3.value, s.s3.root) - - sm.clearCaches() - await storageEval(sm, address, key, s.s3.value, s.s3.root) - }) - - it(`CP -> S1.1 -> S1.2 -> Commit -> Flush() (-> S1.2)`, async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.checkpoint() - await sm.putContractStorage(address, key, s.s1.value) - await sm.putContractStorage(address, key, s.s2.value) - await sm.commit() - await sm.flush() - await storageEval(sm, address, key, s.s2.value, s.s2.root) - - sm.clearCaches() - await storageEval(sm, address, key, s.s2.value, s.s2.root) - }) - - it(`CP -> S1.1 -> S1.2 -> Revert -> Flush() (-> Undefined)`, async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.checkpoint() - await sm.putContractStorage(address, key, s.s1.value) - - await sm.putContractStorage(address, key, s.s2.value) - await sm.revert() - await sm.flush() - await storageEval(sm, address, key, valueEmpty, rootEmpty) - - sm.clearCaches() - await storageEval(sm, address, key, valueEmpty, rootEmpty) - }) - - it(`S1.1 -> CP -> S1.2 -> Revert -> Flush() (-> S1.1)`, async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.putContractStorage(address, key, s.s1.value) - await sm.checkpoint() - await sm.putContractStorage(address, key, s.s2.value) - await sm.revert() - await sm.flush() - await storageEval(sm, address, key, s.s1.value, s.s1.root) - - sm.clearCaches() - await storageEval(sm, address, key, s.s1.value, s.s1.root) - }) - - it('S1.1 -> CP -> S1.2 -> CP -> S1.3 -> Commit -> Commit -> Flush() (-> S1.3)', async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.putContractStorage(address, key, s.s1.value) - await sm.checkpoint() - await sm.putContractStorage(address, key, s.s2.value) - await sm.checkpoint() - await sm.putContractStorage(address, key, s.s3.value) - await sm.commit() - await sm.commit() - await sm.flush() - await storageEval(sm, address, key, s.s3.value, s.s3.root) - - sm.clearCaches() - await storageEval(sm, address, key, s.s3.value, s.s3.root) - }) - - it('S1.1 -> CP -> S1.2 -> CP -> S1.3 -> Commit -> Revert -> Flush() (-> S1.1)', async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.putContractStorage(address, key, s.s1.value) - await sm.checkpoint() - await sm.putContractStorage(address, key, s.s2.value) - await sm.checkpoint() - await sm.putContractStorage(address, key, s.s3.value) - await sm.commit() - await sm.revert() - await sm.flush() - await storageEval(sm, address, key, s.s1.value, s.s1.root) - - sm.clearCaches() - await storageEval(sm, address, key, s.s1.value, s.s1.root) - }) - - it('S1.1 -> CP -> S1.2 -> CP -> S1.3 -> Revert -> Commit -> Flush() (-> S1.2)', async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.putContractStorage(address, key, s.s1.value) - await sm.checkpoint() - await sm.putContractStorage(address, key, s.s2.value) - await sm.checkpoint() - await sm.putContractStorage(address, key, s.s3.value) - await sm.revert() - await sm.commit() - await sm.flush() - await storageEval(sm, address, key, s.s2.value, s.s2.root) - - sm.clearCaches() - await storageEval(sm, address, key, s.s2.value, s.s2.root) - }) - - it('S1.1 -> CP -> S1.2 -> CP -> S1.3 -> Revert -> S1.4 -> Commit -> Flush() (-> S1.4)', async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.putContractStorage(address, key, s.s1.value) - await sm.checkpoint() - await sm.putContractStorage(address, key, s.s2.value) - await sm.checkpoint() - await sm.putContractStorage(address, key, s.s3.value) - await sm.revert() - await sm.putContractStorage(address, key, s.s4.value) - await sm.commit() - await sm.flush() - await storageEval(sm, address, key, s.s4.value, s.s4.root) - - sm.clearCaches() - await storageEval(sm, address, key, s.s4.value, s.s4.root) - }) - - it('S1.1 -> CP -> S1.2 -> CP -> S1.3 -> Revert -> S1.4 -> CP -> S1.5 -> Commit -> Commit -> Flush() (-> S1.5)', async () => { - const sm = new DefaultStateManager() - await sm.putAccount(address, account) - - await sm.putContractStorage(address, key, s.s1.value) - await sm.checkpoint() - await sm.putContractStorage(address, key, s.s2.value) - await sm.checkpoint() - await sm.putContractStorage(address, key, s.s3.value) - await sm.revert() - await sm.putContractStorage(address, key, s.s4.value) - await sm.checkpoint() - await sm.putContractStorage(address, key, s.s5.value) - await sm.commit() - await sm.commit() - await sm.flush() - await storageEval(sm, address, key, s.s5.value, s.s5.root) - - sm.clearCaches() - await storageEval(sm, address, key, s.s5.value, s.s5.root) - }) + for (const SMDict of stateManagers) { + for (const s of storageSets) { + it(`No CP -> S1 -> Flush() (-> S1)`, async () => { + const sm = new SMDict.SM() + await sm.putAccount(address, new Account()) + + await sm.putContractStorage(address, key, s.s1.value) + await sm.flush() + await storageEval(sm, address, key, s.s1.value, s.s1.root, SMDict.rootCheck) + + sm.clearCaches() + assert.deepEqual(await sm.getContractStorage(address, key), s.s1.value) + await storageEval(sm, address, key, s.s1.value, s.s1.root, SMDict.rootCheck) + }) + + it(`CP -> S1.1 -> Commit -> Flush() (-> S1.1)`, async () => { + const sm = new SMDict.SM() + await sm.putAccount(address, new Account()) + + await sm.checkpoint() + await sm.putContractStorage(address, key, s.s1.value) + await sm.commit() + await sm.flush() + await storageEval(sm, address, key, s.s1.value, s.s1.root, SMDict.rootCheck) + + sm.clearCaches() + await storageEval(sm, address, key, s.s1.value, s.s1.root, SMDict.rootCheck) + }) + + it(`CP -> S1.1 -> Revert -> Flush() (-> Undefined)`, async () => { + const sm = new SMDict.SM() + await sm.putAccount(address, new Account()) + + await sm.checkpoint() + await sm.putContractStorage(address, key, s.s1.value) + + await sm.revert() + await sm.flush() + await storageEval(sm, address, key, valueEmpty, rootEmpty) + + sm.clearCaches() + + await storageEval(sm, address, key, valueEmpty, rootEmpty) + }) + + it(`S1.1 -> CP -> Commit -> Flush() (-> S1.1)`, async () => { + const sm = new SMDict.SM() + await sm.putAccount(address, new Account()) + + await sm.putContractStorage(address, key, s.s1.value) + await sm.checkpoint() + await sm.commit() + await sm.flush() + await storageEval(sm, address, key, s.s1.value, s.s1.root, SMDict.rootCheck) + + sm.clearCaches() + await storageEval(sm, address, key, s.s1.value, s.s1.root, SMDict.rootCheck) + }) + + it(`S1.1 -> CP -> Revert -> Flush() (-> S1.1)`, async () => { + const sm = new SMDict.SM() + await sm.putAccount(address, new Account()) + + await sm.putContractStorage(address, key, s.s1.value) + await sm.checkpoint() + await sm.revert() + await sm.flush() + await storageEval(sm, address, key, s.s1.value, s.s1.root, SMDict.rootCheck) + + sm.clearCaches() + await storageEval(sm, address, key, s.s1.value, s.s1.root, SMDict.rootCheck) + }) + + it(`S1.1 -> CP -> S1.2 -> Commit -> Flush() (-> S1.2)`, async () => { + const sm = new SMDict.SM() + await sm.putAccount(address, new Account()) + + await sm.putContractStorage(address, key, s.s1.value) + await sm.checkpoint() + await sm.putContractStorage(address, key, s.s2.value) + await sm.commit() + await sm.flush() + await storageEval(sm, address, key, s.s2.value, s.s2.root, SMDict.rootCheck) + + sm.clearCaches() + await storageEval(sm, address, key, s.s2.value, s.s2.root, SMDict.rootCheck) + }) + + it(`S1.1 -> CP -> S1.2 -> Commit -> S1.3 -> Flush() (-> S1.3)`, async () => { + const sm = new SMDict.SM() + await sm.putAccount(address, new Account()) + + await sm.putContractStorage(address, key, s.s1.value) + await sm.checkpoint() + await sm.putContractStorage(address, key, s.s2.value) + await sm.commit() + await sm.putContractStorage(address, key, s.s3.value) + await sm.flush() + await storageEval(sm, address, key, s.s3.value, s.s3.root, SMDict.rootCheck) + + sm.clearCaches() + await storageEval(sm, address, key, s.s3.value, s.s3.root, SMDict.rootCheck) + }) + + it(`S1.1 -> CP -> S1.2 -> S1.3 -> Commit -> Flush() (-> S1.3)`, async () => { + const sm = new SMDict.SM() + await sm.putAccount(address, new Account()) + + await sm.putContractStorage(address, key, s.s1.value) + await sm.checkpoint() + await sm.putContractStorage(address, key, s.s2.value) + await sm.putContractStorage(address, key, s.s3.value) + await sm.commit() + await sm.flush() + await storageEval(sm, address, key, s.s3.value, s.s3.root, SMDict.rootCheck) + + sm.clearCaches() + await storageEval(sm, address, key, s.s3.value, s.s3.root, SMDict.rootCheck) + }) + + it(`CP -> S1.1 -> S1.2 -> Commit -> Flush() (-> S1.2)`, async () => { + const sm = new SMDict.SM() + await sm.putAccount(address, new Account()) + + await sm.checkpoint() + await sm.putContractStorage(address, key, s.s1.value) + await sm.putContractStorage(address, key, s.s2.value) + await sm.commit() + await sm.flush() + await storageEval(sm, address, key, s.s2.value, s.s2.root, SMDict.rootCheck) + + sm.clearCaches() + await storageEval(sm, address, key, s.s2.value, s.s2.root, SMDict.rootCheck) + }) + + it(`CP -> S1.1 -> S1.2 -> Revert -> Flush() (-> Undefined)`, async () => { + const sm = new SMDict.SM() + await sm.putAccount(address, new Account()) + + await sm.checkpoint() + await sm.putContractStorage(address, key, s.s1.value) + + await sm.putContractStorage(address, key, s.s2.value) + await sm.revert() + await sm.flush() + await storageEval(sm, address, key, valueEmpty, rootEmpty) + + sm.clearCaches() + await storageEval(sm, address, key, valueEmpty, rootEmpty) + }) + + it(`S1.1 -> CP -> S1.2 -> Revert -> Flush() (-> S1.1)`, async () => { + const sm = new SMDict.SM() + await sm.putAccount(address, new Account()) + + await sm.putContractStorage(address, key, s.s1.value) + await sm.checkpoint() + await sm.putContractStorage(address, key, s.s2.value) + await sm.revert() + await sm.flush() + await storageEval(sm, address, key, s.s1.value, s.s1.root, SMDict.rootCheck) + + sm.clearCaches() + await storageEval(sm, address, key, s.s1.value, s.s1.root, SMDict.rootCheck) + }) + + it('S1.1 -> CP -> S1.2 -> CP -> S1.3 -> Commit -> Commit -> Flush() (-> S1.3)', async () => { + const sm = new SMDict.SM() + await sm.putAccount(address, new Account()) + + await sm.putContractStorage(address, key, s.s1.value) + await sm.checkpoint() + await sm.putContractStorage(address, key, s.s2.value) + await sm.checkpoint() + await sm.putContractStorage(address, key, s.s3.value) + await sm.commit() + await sm.commit() + await sm.flush() + await storageEval(sm, address, key, s.s3.value, s.s3.root, SMDict.rootCheck) + + sm.clearCaches() + await storageEval(sm, address, key, s.s3.value, s.s3.root, SMDict.rootCheck) + }) + + it('S1.1 -> CP -> S1.2 -> CP -> S1.3 -> Commit -> Revert -> Flush() (-> S1.1)', async () => { + const sm = new SMDict.SM() + await sm.putAccount(address, new Account()) + + await sm.putContractStorage(address, key, s.s1.value) + await sm.checkpoint() + await sm.putContractStorage(address, key, s.s2.value) + await sm.checkpoint() + await sm.putContractStorage(address, key, s.s3.value) + await sm.commit() + await sm.revert() + await sm.flush() + await storageEval(sm, address, key, s.s1.value, s.s1.root, SMDict.rootCheck) + + sm.clearCaches() + await storageEval(sm, address, key, s.s1.value, s.s1.root, SMDict.rootCheck) + }) + + it('S1.1 -> CP -> S1.2 -> CP -> S1.3 -> Revert -> Commit -> Flush() (-> S1.2)', async () => { + const sm = new SMDict.SM() + await sm.putAccount(address, new Account()) + + await sm.putContractStorage(address, key, s.s1.value) + await sm.checkpoint() + await sm.putContractStorage(address, key, s.s2.value) + await sm.checkpoint() + await sm.putContractStorage(address, key, s.s3.value) + await sm.revert() + await sm.commit() + await sm.flush() + await storageEval(sm, address, key, s.s2.value, s.s2.root, SMDict.rootCheck) + + sm.clearCaches() + await storageEval(sm, address, key, s.s2.value, s.s2.root, SMDict.rootCheck) + }) + + it('S1.1 -> CP -> S1.2 -> CP -> S1.3 -> Revert -> S1.4 -> Commit -> Flush() (-> S1.4)', async () => { + const sm = new SMDict.SM() + await sm.putAccount(address, new Account()) + + await sm.putContractStorage(address, key, s.s1.value) + await sm.checkpoint() + await sm.putContractStorage(address, key, s.s2.value) + await sm.checkpoint() + await sm.putContractStorage(address, key, s.s3.value) + await sm.revert() + await sm.putContractStorage(address, key, s.s4.value) + await sm.commit() + await sm.flush() + await storageEval(sm, address, key, s.s4.value, s.s4.root, SMDict.rootCheck) + + sm.clearCaches() + await storageEval(sm, address, key, s.s4.value, s.s4.root, SMDict.rootCheck) + }) + + it('S1.1 -> CP -> S1.2 -> CP -> S1.3 -> Revert -> S1.4 -> CP -> S1.5 -> Commit -> Commit -> Flush() (-> S1.5)', async () => { + const sm = new SMDict.SM() + await sm.putAccount(address, new Account()) + + await sm.putContractStorage(address, key, s.s1.value) + await sm.checkpoint() + await sm.putContractStorage(address, key, s.s2.value) + await sm.checkpoint() + await sm.putContractStorage(address, key, s.s3.value) + await sm.revert() + await sm.putContractStorage(address, key, s.s4.value) + await sm.checkpoint() + await sm.putContractStorage(address, key, s.s5.value) + await sm.commit() + await sm.commit() + await sm.flush() + await storageEval(sm, address, key, s.s5.value, s.s5.root, SMDict.rootCheck) + + sm.clearCaches() + await storageEval(sm, address, key, s.s5.value, s.s5.root, SMDict.rootCheck) + }) + } } })