diff --git a/package-lock.json b/package-lock.json index e0acd5abf0..86a737fc1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13783,7 +13783,6 @@ "license": "MPL-2.0", "dependencies": { "@ethereumjs/block": "^5.3.0", - "@ethereumjs/blockchain": "^7.3.0", "@ethereumjs/common": "^4.4.0", "@ethereumjs/evm": "^3.1.0", "@ethereumjs/rlp": "^5.0.2", @@ -13795,6 +13794,7 @@ "ethereum-cryptography": "^2.2.1" }, "devDependencies": { + "@ethereumjs/blockchain": "^7.3.0", "@ethereumjs/ethash": "^3.0.3", "@ethersproject/abi": "^5.0.12", "@types/benchmark": "^1.0.33", diff --git a/packages/block/src/block/constructors.ts b/packages/block/src/block/constructors.ts index bab7a78495..62ac8ee0cb 100644 --- a/packages/block/src/block/constructors.ts +++ b/packages/block/src/block/constructors.ts @@ -119,6 +119,18 @@ export function createBlock(blockData: BlockData = {}, opts?: BlockOptions) { ) } +/** + * Simple static constructor if only an empty block is needed + * (tree shaking advantages since it does not draw all the tx constructors in) + * + * @param headerData + * @param opts + */ +export function createEmptyBlock(headerData: HeaderData, opts?: BlockOptions) { + const header = createBlockHeader(headerData, opts) + return new Block(header) +} + /** * Static constructor to create a block from an array of Bytes values * diff --git a/packages/block/test/block.spec.ts b/packages/block/test/block.spec.ts index b830005b7f..7a3cb016f7 100644 --- a/packages/block/test/block.spec.ts +++ b/packages/block/test/block.spec.ts @@ -20,6 +20,7 @@ import { createBlockFromBytesArray, createBlockFromRLPSerializedBlock, createBlockFromRPC, + createEmptyBlock, paramsBlock, } from '../src/index.js' @@ -46,6 +47,9 @@ describe('[Block]: block functions', () => { 'should use custom parameters provided', ) + const emptyBlock = createEmptyBlock({}, { common }) + assert.ok(bytesToHex(emptyBlock.hash()), 'block should initialize') + // test default freeze values // also test if the options are carried over to the constructor block = createBlock({}) diff --git a/packages/client/src/execution/vmexecution.ts b/packages/client/src/execution/vmexecution.ts index 203910a504..d35af98ce7 100644 --- a/packages/client/src/execution/vmexecution.ts +++ b/packages/client/src/execution/vmexecution.ts @@ -221,7 +221,7 @@ export class VMExecution extends Execution { } async transitionToVerkle(merkleStateRoot: Uint8Array, assignToVM: boolean = true): Promise { - if (this.vm.stateManager instanceof StatelessVerkleStateManager) { + if (typeof this.vm.stateManager.initVerkleExecutionWitness === 'function') { return } @@ -230,15 +230,19 @@ export class VMExecution extends Execution { await this.setupMerkleVM() } const merkleVM = this.merkleVM! - const merkleStateManager = merkleVM.stateManager as DefaultStateManager + const merkleStateManager = merkleVM.stateManager if (this.verkleVM === undefined) { await this.setupVerkleVM() } const verkleVM = this.verkleVM! - const verkleStateManager = verkleVM.stateManager as StatelessVerkleStateManager + const verkleStateManager = verkleVM.stateManager - const verkleStateRoot = await verkleStateManager.getTransitionStateRoot( + // TODO: can we please implement this in a different way and not introduce a method + // *inside* one state manager which takes another state manager? + // That bloats the interface too much, this should be minimally a separate util method + // or fully move to client + const verkleStateRoot = await (verkleStateManager as any).getTransitionStateRoot( merkleStateManager, merkleStateRoot, ) @@ -416,7 +420,8 @@ export class VMExecution extends Execution { vm = this.verkleVM } - const needsStatelessExecution = vm.stateManager instanceof StatelessVerkleStateManager + const needsStatelessExecution = + typeof this.vm.stateManager.initVerkleExecutionWitness === 'function' if (needsStatelessExecution && block.executionWitness === undefined) { throw Error(`Verkle blocks need executionWitness for stateless execution`) } else { @@ -576,7 +581,7 @@ export class VMExecution extends Execution { this.config.logger.warn( `Setting execution head to hash=${short(jumpToHash)} number=${jumpToNumber}`, ) - await this.vm.blockchain.setIteratorHead('vm', jumpToHash) + await this.chain.blockchain.setIteratorHead('vm', jumpToHash) }) } @@ -595,19 +600,9 @@ export class VMExecution extends Execution { this.running = true let numExecuted: number | null | undefined = undefined - const { blockchain } = this.vm - if (typeof blockchain.getIteratorHead !== 'function') { - throw new Error('cannot get iterator head: blockchain has no getIteratorHead function') - } - let startHeadBlock = await blockchain.getIteratorHead() + let startHeadBlock = await this.chain.blockchain.getIteratorHead() await this.checkAndReset(startHeadBlock) - - if (typeof blockchain.getCanonicalHeadBlock !== 'function') { - throw new Error( - 'cannot get iterator head: blockchain has no getCanonicalHeadBlock function', - ) - } - let canonicalHead = await blockchain.getCanonicalHeadBlock() + let canonicalHead = await this.chain.blockchain.getCanonicalHeadBlock() this.config.logger.debug( `Running execution startHeadBlock=${startHeadBlock?.header.number} canonicalHead=${canonicalHead?.header.number} loop=${loop}`, @@ -637,14 +632,14 @@ export class VMExecution extends Execution { headBlock = undefined parentState = undefined errorBlock = undefined - this.vmPromise = blockchain + this.vmPromise = this.chain.blockchain .iterator( 'vm', async (block: Block, reorg: boolean) => { // determine starting state for block run // if we are just starting or if a chain reorg has happened if (headBlock === undefined || reorg) { - headBlock = await blockchain.getBlock(block.header.parentHash) + headBlock = await this.chain.blockchain.getBlock(block.header.parentHash) parentState = headBlock.header.stateRoot if (reorg) { @@ -664,11 +659,6 @@ export class VMExecution extends Execution { // run block, update head if valid try { const { number, timestamp } = block.header - if (typeof blockchain.getTotalDifficulty !== 'function') { - throw new Error( - 'cannot get iterator head: blockchain has no getTotalDifficulty function', - ) - } const hardfork = this.config.execCommon.getHardforkBy({ blockNumber: number, @@ -692,7 +682,7 @@ export class VMExecution extends Execution { } if ( (!this.config.execCommon.gteHardfork(Hardfork.Osaka) && - this.vm.stateManager instanceof StatelessVerkleStateManager) || + typeof this.vm.stateManager.initVerkleExecutionWitness === 'function') || (this.config.execCommon.gteHardfork(Hardfork.Osaka) && this.vm.stateManager instanceof DefaultStateManager) ) { @@ -800,7 +790,7 @@ export class VMExecution extends Execution { backStepToHash ?? 'na', )} hasParentStateRoot=${short(backStepToRoot ?? 'na')}:\n${error}`, ) - await this.vm.blockchain.setIteratorHead('vm', backStepToHash) + await this.chain.blockchain.setIteratorHead('vm', backStepToHash) } else { this.config.logger.error( `${errorMsg}, couldn't back step to vmHead number=${backStepTo} hash=${short( @@ -855,14 +845,7 @@ export class VMExecution extends Execution { numExecuted = await this.vmPromise if (numExecuted !== null) { - let endHeadBlock - if (typeof this.vm.blockchain.getIteratorHead === 'function') { - endHeadBlock = await this.vm.blockchain.getIteratorHead('vm') - } else { - throw new Error( - 'cannot get iterator head: blockchain has no getIteratorHead function', - ) - } + const endHeadBlock = await this.chain.blockchain.getIteratorHead('vm') if (typeof numExecuted === 'number' && numExecuted > 0) { const firstNumber = startHeadBlock.header.number @@ -891,12 +874,7 @@ export class VMExecution extends Execution { ) } startHeadBlock = endHeadBlock - if (typeof this.vm.blockchain.getCanonicalHeadBlock !== 'function') { - throw new Error( - 'cannot get iterator head: blockchain has no getCanonicalHeadBlock function', - ) - } - canonicalHead = await this.vm.blockchain.getCanonicalHeadBlock() + canonicalHead = await this.chain.blockchain.getCanonicalHeadBlock() } } @@ -917,19 +895,12 @@ export class VMExecution extends Execution { this.STATS_INTERVAL, ) - const { blockchain } = this.vm if (this.running || !this.started) { return false } - if (typeof blockchain.getIteratorHead !== 'function') { - throw new Error('cannot get iterator head: blockchain has no getIteratorHead function') - } - const vmHeadBlock = await blockchain.getIteratorHead() - if (typeof blockchain.getCanonicalHeadBlock !== 'function') { - throw new Error('cannot get iterator head: blockchain has no getCanonicalHeadBlock function') - } - const canonicalHead = await blockchain.getCanonicalHeadBlock() + const vmHeadBlock = await this.chain.blockchain.getIteratorHead() + const canonicalHead = await this.chain.blockchain.getCanonicalHeadBlock() const infoStr = `vmHead=${vmHeadBlock.header.number} canonicalHead=${ canonicalHead.header.number @@ -984,7 +955,7 @@ export class VMExecution extends Execution { async executeBlocks(first: number, last: number, txHashes: string[]) { this.config.logger.info('Preparing for block execution (debug mode, no services started)...') - const block = await this.vm.blockchain.getBlock(first) + const block = await this.chain.blockchain.getBlock(first) const startExecutionHardfork = this.config.execCommon.getHardforkBy({ blockNumber: block.header.number, timestamp: block.header.timestamp, @@ -1000,13 +971,10 @@ export class VMExecution extends Execution { const vm = await this.vm.shallowCopy(false) for (let blockNumber = first; blockNumber <= last; blockNumber++) { - const block = await vm.blockchain.getBlock(blockNumber) - const parentBlock = await vm.blockchain.getBlock(block.header.parentHash) + const block = await this.chain.blockchain.getBlock(blockNumber) + const parentBlock = await this.chain.blockchain.getBlock(block.header.parentHash) // Set the correct state root const root = parentBlock.header.stateRoot - if (typeof vm.blockchain.getTotalDifficulty !== 'function') { - throw new Error('cannot get iterator head: blockchain has no getTotalDifficulty function') - } vm.common.setHardforkBy({ blockNumber, timestamp: block.header.timestamp, diff --git a/packages/client/src/miner/miner.ts b/packages/client/src/miner/miner.ts index 271c92b92d..7e6e5124a2 100644 --- a/packages/client/src/miner/miner.ts +++ b/packages/client/src/miner/miner.ts @@ -12,7 +12,7 @@ import type { Config } from '../config.js' import type { VMExecution } from '../execution/index.js' import type { FullEthereumService } from '../service/index.js' import type { FullSynchronizer } from '../sync/index.js' -import type { CliqueConsensus } from '@ethereumjs/blockchain' +import type { Blockchain, CliqueConsensus } from '@ethereumjs/blockchain' import type { CliqueConfig } from '@ethereumjs/common' import type { Miner as EthashMiner, Solution } from '@ethereumjs/ethash' @@ -244,10 +244,9 @@ export class Miner { const [signerAddress, signerPrivKey] = this.config.accounts[0] cliqueSigner = signerPrivKey // Determine if signer is INTURN (2) or NOTURN (1) - inTurn = await (vmCopy.blockchain.consensus as CliqueConsensus).cliqueSignerInTurn( - signerAddress, - number, - ) + inTurn = await ( + (vmCopy.blockchain as Blockchain).consensus as CliqueConsensus + ).cliqueSignerInTurn(signerAddress, number) difficulty = inTurn ? 2 : 1 } diff --git a/packages/client/src/miner/pendingBlock.ts b/packages/client/src/miner/pendingBlock.ts index 4f901aea20..ce795435be 100644 --- a/packages/client/src/miner/pendingBlock.ts +++ b/packages/client/src/miner/pendingBlock.ts @@ -104,9 +104,6 @@ export class PendingBlock { const { timestamp, mixHash, parentBeaconBlockRoot, coinbase } = headerData let { gasLimit } = parentBlock.header - if (typeof vm.blockchain.getTotalDifficulty !== 'function') { - throw new Error('cannot get iterator head: blockchain has no getTotalDifficulty function') - } vm.common.setHardforkBy({ blockNumber: number, timestamp, diff --git a/packages/client/test/miner/miner.spec.ts b/packages/client/test/miner/miner.spec.ts index ec11116b48..e1f2dcf256 100644 --- a/packages/client/test/miner/miner.spec.ts +++ b/packages/client/test/miner/miner.spec.ts @@ -22,7 +22,7 @@ import { wait } from '../integration/util.js' import type { FullSynchronizer } from '../../src/sync/index.js' import type { Block } from '@ethereumjs/block' -import type { CliqueConsensus } from '@ethereumjs/blockchain' +import type { Blockchain, CliqueConsensus } from '@ethereumjs/blockchain' import type { VM } from '@ethereumjs/vm' const A = { @@ -242,7 +242,9 @@ describe('assembleBlocks() -> with a single tx', async () => { await txPool.add(txA01) // disable consensus to skip PoA block signer validation - ;(vm.blockchain.consensus as CliqueConsensus).cliqueActiveSigners = () => [A.address] // stub + ;((vm.blockchain as Blockchain).consensus as CliqueConsensus).cliqueActiveSigners = () => [ + A.address, + ] // stub chain.putBlocks = (blocks: Block[]) => { it('should include tx in new block', () => { @@ -280,7 +282,9 @@ describe('assembleBlocks() -> with a hardfork mismatching tx', async () => { }) // disable consensus to skip PoA block signer validation - ;(vm.blockchain.consensus as CliqueConsensus).cliqueActiveSigners = () => [A.address] // stub + ;((vm.blockchain as Blockchain).consensus as CliqueConsensus).cliqueActiveSigners = () => [ + A.address, + ] // stub chain.putBlocks = (blocks: Block[]) => { it('should not include tx', () => { diff --git a/packages/client/test/miner/pendingBlock.spec.ts b/packages/client/test/miner/pendingBlock.spec.ts index f7ab2b5633..f30b12681b 100644 --- a/packages/client/test/miner/pendingBlock.spec.ts +++ b/packages/client/test/miner/pendingBlock.spec.ts @@ -1,4 +1,5 @@ -import { Block, BlockHeader, createBlockHeader } from '@ethereumjs/block' +import { BlockHeader, createBlockHeader } from '@ethereumjs/block' +import { createBlockchain } from '@ethereumjs/blockchain' import { Common, Goerli, Hardfork, Mainnet, createCommonFromGethGenesis } from '@ethereumjs/common' import { DefaultStateManager } from '@ethereumjs/statemanager' import { createBlob4844Tx, createFeeMarket1559Tx, createLegacyTx } from '@ethereumjs/tx' @@ -25,6 +26,7 @@ import { PendingBlock } from '../../src/miner/index.js' import { TxPool } from '../../src/service/txpool.js' import { mockBlockchain } from '../rpc/mockBlockchain.js' +import type { Blockchain } from '@ethereumjs/blockchain' import type { TypedTransaction } from '@ethereumjs/tx' const A = { @@ -124,14 +126,15 @@ describe('[PendingBlock]', async () => { it('should start and build', async () => { const { txPool } = setup() - const vm = await VM.create({ common }) + const blockchain = await createBlockchain({ common }) + const vm = await VM.create({ common, blockchain }) await setBalance(vm, A.address, BigInt(5000000000000000)) await setBalance(vm, B.address, BigInt(5000000000000000)) await txPool.add(txA01) await txPool.add(txA02) // skip hardfork validation for ease const pendingBlock = new PendingBlock({ config, txPool, skipHardForkValidation: true }) - const parentBlock = await vm.blockchain.getCanonicalHeadBlock!() + const parentBlock = await (vm.blockchain as Blockchain).getCanonicalHeadBlock!() const payloadId = await pendingBlock.start(vm, parentBlock) assert.equal(pendingBlock.pendingPayloads.size, 1, 'should set the pending payload') await txPool.add(txB01) @@ -151,7 +154,8 @@ describe('[PendingBlock]', async () => { it('should include txs with mismatching hardforks that can still be executed', async () => { const { txPool } = setup() - const vm = await VM.create({ common }) + const blockchain = await createBlockchain({ common }) + const vm = await VM.create({ common, blockchain }) await setBalance(vm, A.address, BigInt(5000000000000000)) await setBalance(vm, B.address, BigInt(5000000000000000)) @@ -160,7 +164,7 @@ describe('[PendingBlock]', async () => { assert.equal(txPool.txsInPool, 1, '1 txA011 should be added') // skip hardfork validation for ease const pendingBlock = new PendingBlock({ config, txPool }) - const parentBlock = await vm.blockchain.getCanonicalHeadBlock!() + const parentBlock = await (vm.blockchain as Blockchain).getCanonicalHeadBlock!() const payloadId = await pendingBlock.start(vm, parentBlock) assert.equal(pendingBlock.pendingPayloads.size, 1, 'should set the pending payload') const payload = pendingBlock.pendingPayloads.get(bytesToHex(payloadId)) @@ -199,9 +203,10 @@ describe('[PendingBlock]', async () => { const { txPool } = setup() await txPool.add(txA01) const pendingBlock = new PendingBlock({ config, txPool, skipHardForkValidation: true }) - const vm = await VM.create({ common }) + const blockchain = await createBlockchain({ common }) + const vm = await VM.create({ common, blockchain }) await setBalance(vm, A.address, BigInt(5000000000000000)) - const parentBlock = await vm.blockchain.getCanonicalHeadBlock!() + const parentBlock = await (vm.blockchain as Blockchain).getCanonicalHeadBlock!() const payloadId = await pendingBlock.start(vm, parentBlock) assert.equal(pendingBlock.pendingPayloads.size, 1, 'should set the pending payload') pendingBlock.stop(payloadId) @@ -219,7 +224,8 @@ describe('[PendingBlock]', async () => { const prevGasLimit = common['_chainParams'].genesis.gasLimit common['_chainParams'].genesis.gasLimit = 50000 - const vm = await VM.create({ common }) + const blockchain = await createBlockchain({ common }) + const vm = await VM.create({ common, blockchain }) await setBalance(vm, A.address, BigInt(5000000000000000)) // create alternate transactions with custom gas limits to @@ -242,7 +248,7 @@ describe('[PendingBlock]', async () => { await txPool.add(txA03) const pendingBlock = new PendingBlock({ config, txPool, skipHardForkValidation: true }) await setBalance(vm, A.address, BigInt(5000000000000000)) - const parentBlock = await vm.blockchain.getCanonicalHeadBlock!() + const parentBlock = await (vm.blockchain as Blockchain).getCanonicalHeadBlock!() const payloadId = await pendingBlock.start(vm, parentBlock) assert.equal(pendingBlock.pendingPayloads.size, 1, 'should set the pending payload') @@ -270,7 +276,8 @@ describe('[PendingBlock]', async () => { it('should skip adding txs when tx too big to fit', async () => { const { txPool } = setup() - const vm = await VM.create({ common }) + const blockchain = await createBlockchain({ common }) + const vm = await VM.create({ common, blockchain }) await setBalance(vm, A.address, BigInt(5000000000000000)) await txPool.add(txA01) await txPool.add(txA02) @@ -286,7 +293,7 @@ describe('[PendingBlock]', async () => { await txPool.add(txA03) const pendingBlock = new PendingBlock({ config, txPool, skipHardForkValidation: true }) await setBalance(vm, A.address, BigInt(5000000000000000)) - const parentBlock = await vm.blockchain.getCanonicalHeadBlock!() + const parentBlock = await (vm.blockchain as Blockchain).getCanonicalHeadBlock!() const payloadId = await pendingBlock.start(vm, parentBlock) assert.equal(pendingBlock.pendingPayloads.size, 1, 'should set the pending payload') const built = await pendingBlock.build(payloadId) @@ -311,8 +318,9 @@ describe('[PendingBlock]', async () => { const { txPool } = setup() await txPool.add(txA01) const pendingBlock = new PendingBlock({ config, txPool, skipHardForkValidation: true }) - const vm = await VM.create({ common }) - const parentBlock = await vm.blockchain.getCanonicalHeadBlock!() + const blockchain = await createBlockchain({ common }) + const vm = await VM.create({ common, blockchain }) + const parentBlock = await (vm.blockchain as Blockchain).getCanonicalHeadBlock!() const payloadId = await pendingBlock.start(vm, parentBlock) assert.equal(pendingBlock.pendingPayloads.size, 1, 'should set the pending payload') const built = await pendingBlock.build(payloadId) @@ -333,23 +341,6 @@ describe('[PendingBlock]', async () => { ) }) - it('should throw when blockchain does not have getTotalDifficulty function', async () => { - const { txPool } = setup() - const pendingBlock = new PendingBlock({ config, txPool, skipHardForkValidation: true }) - const vm = txPool['service'].execution.vm - // override total difficulty function to trigger error case - vm.blockchain.getTotalDifficulty = undefined - try { - await pendingBlock.start(vm, new Block()) - assert.fail('should have thrown') - } catch (err: any) { - assert.equal( - err.message, - 'cannot get iterator head: blockchain has no getTotalDifficulty function', - ) - } - }) - it('construct blob bundles', async () => { const kzg = await loadKZG() const common = createCommonFromGethGenesis(gethGenesis, { @@ -407,9 +398,10 @@ describe('[PendingBlock]', async () => { assert.equal(txPool.txsInPool, 4, '4 txs should still be in the pool') const pendingBlock = new PendingBlock({ config, txPool }) - const vm = await VM.create({ common }) + const blockchain = await createBlockchain({ common }) + const vm = await VM.create({ common, blockchain }) await setBalance(vm, A.address, BigInt(500000000000000000)) - const parentBlock = await vm.blockchain.getCanonicalHeadBlock!() + const parentBlock = await (vm.blockchain as Blockchain).getCanonicalHeadBlock!() // stub the vm's common set hf to do nothing but stay in cancun vm.common.setHardforkBy = () => { return vm.common.hardfork() @@ -466,9 +458,10 @@ describe('[PendingBlock]', async () => { assert.equal(txPool.txsInPool, 1, '1 txs should still be in the pool') const pendingBlock = new PendingBlock({ config, txPool }) - const vm = await VM.create({ common }) + const blockchain = await createBlockchain({ common }) + const vm = await VM.create({ common, blockchain }) await setBalance(vm, A.address, BigInt(500000000000000000)) - const parentBlock = await vm.blockchain.getCanonicalHeadBlock!() + const parentBlock = await (vm.blockchain as Blockchain).getCanonicalHeadBlock!() // stub the vm's common set hf to do nothing but stay in cancun vm.common.setHardforkBy = () => { return vm.common.hardfork() diff --git a/packages/common/src/interfaces.ts b/packages/common/src/interfaces.ts index 03f189b57e..b1832d7428 100644 --- a/packages/common/src/interfaces.ts +++ b/packages/common/src/interfaces.ts @@ -2,7 +2,7 @@ * External Interfaces for other EthereumJS libraries */ -import type { Account, Address, PrefixedHexString } from '@ethereumjs/util' +import type { Account, Address, PrefixedHexString, VerkleExecutionWitness } from '@ethereumjs/util' export interface StorageDump { [key: string]: string @@ -161,6 +161,14 @@ export interface StateManagerInterface { } generateCanonicalGenesis?(initState: any): Promise // TODO make input more typesafe // only Verkle/EIP-6800 (experimental) + accessWitness?: AccessWitnessInterface + initVerkleExecutionWitness?( + blockNum: bigint, + executionWitness?: VerkleExecutionWitness | null, + accessWitness?: AccessWitnessInterface, + ): void + verifyVerkleProof?(stateRoot: Uint8Array): boolean + verifyPostState?(): boolean checkChunkWitnessPresent?(contract: Address, programCounter: number): Promise getAppliedKey?(address: Uint8Array): Uint8Array // only for preimages diff --git a/packages/evm/src/constructors.ts b/packages/evm/src/constructors.ts index 381a542806..f6a3e2b24d 100644 --- a/packages/evm/src/constructors.ts +++ b/packages/evm/src/constructors.ts @@ -2,7 +2,7 @@ import { Common, Mainnet } from '@ethereumjs/common' import { SimpleStateManager } from '@ethereumjs/statemanager' import { NobleBN254 } from './precompiles/index.js' -import { DefaultBlockchain } from './types.js' +import { EVMMockBlockchain } from './types.js' import { EVM } from './index.js' @@ -25,7 +25,7 @@ export async function createEVM(createOpts?: EVMOpts) { } if (opts.blockchain === undefined) { - opts.blockchain = new DefaultBlockchain() + opts.blockchain = new EVMMockBlockchain() } if (opts.stateManager === undefined) { diff --git a/packages/evm/src/eof/constants.ts b/packages/evm/src/eof/constants.ts index 42ec1cc74b..b637ac8a16 100644 --- a/packages/evm/src/eof/constants.ts +++ b/packages/evm/src/eof/constants.ts @@ -6,31 +6,31 @@ export const MAGIC = 0x00 export const VERSION = 0x01 // The min/max sizes of valid headers -export const MIN_HEADER_SIZE = 15 // This min size is used to invalidate an invalid container quickly +export const MIN_HEADER_SIZE = 15 // Min size used to invalidate an invalid container quickly export const MAX_HEADER_SIZE = 49152 // Max initcode size, EIP 3860 -export const KIND_TYPE = 0x01 // The type byte of the types section -export const KIND_CODE = 0x02 // The type byte of the code section -export const KIND_CONTAINER = 0x03 // The type byte of the container section (this is the only optional section in the header) -export const KIND_DATA = 0x04 // The type byte of the data section -export const TERMINATOR = 0x00 // The terminator byte of the header +export const KIND_TYPE = 0x01 // Type byte of types section +export const KIND_CODE = 0x02 // Type byte of code section +export const KIND_CONTAINER = 0x03 // Type byte of container section (the only optional section in the header) +export const KIND_DATA = 0x04 // Type byte of data section +export const TERMINATOR = 0x00 // Terminator byte of header -export const TYPE_MIN = 0x0004 // The minimum size of the types section -export const TYPE_MAX = 0x1000 // The maximum size of the types section -export const TYPE_DIVISOR = 4 // The divisor of types: the type section size should be a multiple of this +export const TYPE_MIN = 0x0004 // Minimum size of types section +export const TYPE_MAX = 0x1000 // Maximum size of types section +export const TYPE_DIVISOR = 4 // Divisor of types: the type section size should be a multiple of this -export const CODE_MIN = 0x0001 // The minimum size of the code section +export const CODE_MIN = 0x0001 // Minimum size of code section -export const CODE_SIZE_MIN = 1 // The minimum size of a code section in the body (the actual code) +export const CODE_SIZE_MIN = 1 // Minimum size of a code section in the body (the actual code) -export const CONTAINER_MIN = 0x0001 // The minimum size of the container section -export const CONTAINER_MAX = 0x0100 // The maximum size of the container section +export const CONTAINER_MIN = 0x0001 // Minimum size of container section +export const CONTAINER_MAX = 0x0100 // Maximum size of container section -export const CONTAINER_SIZE_MIN = 1 // The minimum size of a container in the body +export const CONTAINER_SIZE_MIN = 1 // Minimum size of a container in the body // Constants regarding the type section in the body of the container -export const INPUTS_MAX = 0x7f // The maximum amounts of inputs to a code section in the body -export const OUTPUTS_MAX = 0x80 // The maximum amounts of outputs of a code section in the body -// Note: 0x80 is a special amount of outputs, this marks the code section as "terminating". +export const INPUTS_MAX = 0x7f // Max inputs to a code section in the body +export const OUTPUTS_MAX = 0x80 // Max outputs of a code section in the body +// Note: 0x80 special amount, marks the code section as "terminating" // A terminating section will exit the current call frame, such as RETURN / STOP opcodes. It will not RETF to another code section -export const MAX_STACK_HEIGHT = 0x03ff // The maximum stack height of a code section (this enforces that the stack of this section cannot overflow) +export const MAX_STACK_HEIGHT = 0x03ff // Maximum stack height of a code section (enforces that the stack of this section cannot overflow) diff --git a/packages/evm/src/eof/container.ts b/packages/evm/src/eof/container.ts index a2be68d86c..fe4a08e83d 100644 --- a/packages/evm/src/eof/container.ts +++ b/packages/evm/src/eof/container.ts @@ -132,10 +132,10 @@ class StreamReader { * The EOFHeader, describing the header of the EOF container */ class EOFHeader { - typeSize: number // Size of the types section - codeSizes: number[] // Sizes of the code sections - containerSizes: number[] // Sizes of the containers - dataSize: number // Size of the data section + typeSize: number // Size of types section + codeSizes: number[] // Sizes of code sections + containerSizes: number[] // Sizes of containers + dataSize: number // Size of data section dataSizePtr: number // Used to edit the dataSize in RETURNCONTRACT buffer: Uint8Array // The raw buffer of the entire header @@ -143,7 +143,7 @@ class EOFHeader { /** * Create an EOF header. Performs various validation checks inside the constructor - * @param input The input should either be a raw header, or a complete container + * @param input either a raw header or a complete container */ constructor(input: Uint8Array) { if (input.length > MAX_HEADER_SIZE) { @@ -157,7 +157,7 @@ class EOFHeader { if (input.length < 15) { throw new Error('err: container size less than minimum valid size') } - // Verify that the types section is present, and verify that the type section length is valid + // Verify that the types section is present and its length is valid stream.verifyUint(KIND_TYPE, EOFError.KIND_TYPE) const typeSize = stream.readUint16(EOFError.TypeSize) if (typeSize < TYPE_MIN) { @@ -169,7 +169,7 @@ class EOFHeader { if (typeSize > TYPE_MAX) { throw new Error(`err: number of code sections must not exceed 1024 (got ${typeSize})`) } - // Verify that the code section is present, and verify that the code section size is valid + // Verify that the code section is present and its size is valid stream.verifyUint(KIND_CODE, EOFError.KIND_CODE) const codeSize = stream.readUint16(EOFError.CodeSize) if (codeSize < CODE_MIN) { @@ -178,7 +178,7 @@ class EOFHeader { if (codeSize !== typeSize / TYPE_DIVISOR) { validationError(EOFError.TypeSections, typeSize / TYPE_DIVISOR, codeSize) } - // Read the actual code sizes in the code section, and verify that each code section has the minimum size + // Read the actual code sizes in the code section and verify that each section has the minimum size const codeSizes = [] for (let i = 0; i < codeSize; i++) { const codeSectionSize = stream.readUint16(EOFError.CodeSection) @@ -202,7 +202,7 @@ class EOFHeader { validationError(EOFError.ContainerSectionSize) } - // Read the actual container sections, and validate that each container section has the minimum size + // Read the actual container sections and validate that each section has the minimum size for (let i = 0; i < containerSectionSize; i++) { const containerSize = stream.readUint16(EOFError.ContainerSection) @@ -277,19 +277,19 @@ export interface TypeSection { */ class EOFBody { typeSections: TypeSection[] // Array of type sections, used to index the inputs/outputs/max stack height of each section - codeSections: Uint8Array[] // The bytecode of each code section - containerSections: Uint8Array[] // The raw container bytes of each subcontainer + codeSections: Uint8Array[] // Bytecode of each code section + containerSections: Uint8Array[] // Raw container bytes of each subcontainer entireCode: Uint8Array // The `entireCode` are all code sections concatenated - dataSection: Uint8Array // The bytes of the data section - buffer: Uint8Array // The raw bytes of the body + dataSection: Uint8Array // Bytes of the data section + buffer: Uint8Array // Raw bytes of the body txCallData?: Uint8Array // Only available in TxInitmode. The `txCallData` are the dangling bytes after parsing the container, // and these are used for the CALLDATA in the EVM when trying to create a contract via a transaction, and the deployment code is an EOF container constructor( - buf: Uint8Array, // The buffer of the body. This should be the entire body. It is not valid to pass an entire EOF container in here - header: EOFHeader, // The EOFHeader corresponding to this body - eofMode: EOFContainerMode = EOFContainerMode.Default, // The container mode of EOF + buf: Uint8Array, // Buffer of the body. This should be the entire body. It is not valid to pass an entire EOF container in here + header: EOFHeader, // EOFHeader corresponding to this body + eofMode: EOFContainerMode = EOFContainerMode.Default, // Container mode of EOF dataSectionAllowedSmaller = false, // Only for validation: Deployment containers are allowed to have smaller data section size ) { const stream = new StreamReader(buf) diff --git a/packages/evm/src/eof/setup.ts b/packages/evm/src/eof/setup.ts index 6d977ecfb3..5af23327f0 100644 --- a/packages/evm/src/eof/setup.ts +++ b/packages/evm/src/eof/setup.ts @@ -3,7 +3,7 @@ import { EOFContainer, EOFContainerMode } from './container.js' import type { RunState } from '../interpreter.js' /** - * This method setups the EOF inside the EVM. It prepares the `RunState` to start running EVM in EOF mode + * Setup EOF by preparing the `RunState` to run EVM in EOF mode * @param runState Current run state * @param eofMode EOF mode to run in (only changes in case of EOFCREATE) */ @@ -15,13 +15,13 @@ export function setupEOF(runState: RunState, eofMode: EOFContainerMode = EOFCont }, } - // In case that txCallData is set, then set the `callData` of the `env` to this calldata - // This ensures that CALLDATA can be read when deploying EOF contracts using transactions + // In case that txCallData is set, set the `callData` of `env` to this calldata + // This ensures that CALLDATA can be read when deploying EOF contracts using txs if (runState.env.eof.container.body.txCallData !== undefined) { runState.env.callData = runState.env.eof.container.body.txCallData } - // Set the program counter to the first code section + // Set program counter to the first code section const pc = runState.env.eof.container.header.getCodePosition(0) runState.programCounter = pc } diff --git a/packages/evm/src/eof/util.ts b/packages/evm/src/eof/util.ts index b28a5943e0..054760abe7 100644 --- a/packages/evm/src/eof/util.ts +++ b/packages/evm/src/eof/util.ts @@ -7,8 +7,8 @@ export const EOFBYTES = new Uint8Array([FORMAT, MAGIC]) export const EOFHASH = keccak256(EOFBYTES) /** - * Returns `true` if `code` is an EOF contract, returns `false` otherwise - * @param code Code to test if it is EOF + * Returns `true` if `code` is an EOF contract, otherwise `false` + * @param code Code to test */ export function isEOF(code: Uint8Array): boolean { const check = code.subarray(0, EOFBYTES.length) diff --git a/packages/evm/src/eof/verify.ts b/packages/evm/src/eof/verify.ts index 988f1adc04..0d46057658 100644 --- a/packages/evm/src/eof/verify.ts +++ b/packages/evm/src/eof/verify.ts @@ -125,7 +125,7 @@ function validateOpcodes( opcodeNumbers.delete(0xf0) // CREATE opcodeNumbers.delete(0xf5) // CREATE2 - // Note: this name might be misleading since this is the list of opcodes which are OK as final opcodes in a code section + // Note: Name might be misleading since this is the list of opcodes which are OK as final opcodes in a code section // TODO if using stackDelta for EOF it is possible to add a "termination" boolean for the opcode to mark it as terminating // (so no need to generate this set here) const terminatingOpcodes = new Set() @@ -176,11 +176,11 @@ function validateOpcodes( const stackHeightMin: number[] = [inputs] const stackHeightMax: number[] = [inputs] - // This loop will loop over the entire code section and will validate various rules + // Loop over the entire code section and validate various rules // For (most) validation rules, see https://github.com/ipsilon/eof/blob/main/spec/eof.md // For all validation rules per opcode, find the corresponding EIP, the rules are there while (ptr < code.length) { - // This set tracks the successor opcodes of this opcode (for stack purposes) + // Tracks the successor opcodes of this opcode (for stack purposes) const successorSet = new Set() // ReachableOpcodes: this can likely be deleted after implementing the 5450 algorithm @@ -189,8 +189,7 @@ function validateOpcodes( } if (stackHeightMin[ptr] === undefined || stackHeightMax[ptr] === undefined) { - // This error either means that the code is unreachable, - // or it is possible that it is only reachable via a backwards jump + // Code is either unreachable or only reachable via a backwards jump validationError(EOFError.UnreachableCode) } @@ -240,8 +239,8 @@ function validateOpcodes( if (opcode === 0xe0) { // For RJUMP check that the instruction after RJUMP is reachable - // If this is not the case, then it is not yet targeted by a forward jump - // And hence violates the spec + // If not the case then it is not yet targeted by a forward jump + // and hence violates the spec if (!reachableOpcodes.has(ptr + 3) && ptr + 3 < code.length) { // Note: the final condition above ensures that the bytes after ptr are there // This is an edge case, if the container ends with RJUMP (which is valid) diff --git a/packages/evm/src/evm.ts b/packages/evm/src/evm.ts index 508771cc9d..b7482febdf 100644 --- a/packages/evm/src/evm.ts +++ b/packages/evm/src/evm.ts @@ -39,12 +39,12 @@ import type { OpHandler, OpcodeList, OpcodeMap } from './opcodes/index.js' import type { CustomPrecompile, PrecompileFunc } from './precompiles/index.js' import type { Block, - Blockchain, CustomOpcode, EVMBLSInterface, EVMBN254Interface, EVMEvents, EVMInterface, + EVMMockBlockchainInterface, EVMOpts, EVMResult, EVMRunCallOpts, @@ -96,7 +96,7 @@ export class EVM implements EVMInterface { public readonly events: AsyncEventEmitter public stateManager: StateManagerInterface - public blockchain: Blockchain + public blockchain: EVMMockBlockchainInterface public journal: Journal public readonly transientStorage: TransientStorage diff --git a/packages/evm/src/index.ts b/packages/evm/src/index.ts index 0733f6c8ba..2386c948de 100644 --- a/packages/evm/src/index.ts +++ b/packages/evm/src/index.ts @@ -11,12 +11,14 @@ import { RustBN254, getActivePrecompiles, } from './precompiles/index.js' +import { EVMMockBlockchain } from './types.js' import type { InterpreterStep } from './interpreter.js' import type { EVMBLSInterface, EVMBN254Interface, EVMInterface, + EVMMockBlockchainInterface, EVMOpts, EVMResult, EVMRunCallOpts, @@ -30,6 +32,7 @@ export type { EVMBLSInterface, EVMBN254Interface, EVMInterface, + EVMMockBlockchainInterface, EVMOpts, EVMResult, EVMRunCallOpts, @@ -45,6 +48,7 @@ export { EVM, EvmError, EVMErrorMessage, + EVMMockBlockchain, getActivePrecompiles, getOpcodesForHF, MCLBLS, diff --git a/packages/evm/src/interpreter.ts b/packages/evm/src/interpreter.ts index 6b8a3362c4..631e7f2873 100644 --- a/packages/evm/src/interpreter.ts +++ b/packages/evm/src/interpreter.ts @@ -26,7 +26,14 @@ import { Stack } from './stack.js' import type { EVM } from './evm.js' import type { Journal } from './journal.js' import type { AsyncOpHandler, Opcode, OpcodeMapEntry } from './opcodes/index.js' -import type { Block, Blockchain, EOFEnv, EVMProfilerOpts, EVMResult, Log } from './types.js' +import type { + Block, + EOFEnv, + EVMMockBlockchainInterface, + EVMProfilerOpts, + EVMResult, + Log, +} from './types.js' import type { AccessWitnessInterface, Common, StateManagerInterface } from '@ethereumjs/common' import type { Address, PrefixedHexString } from '@ethereumjs/util' @@ -87,7 +94,7 @@ export interface RunState { validJumps: Uint8Array // array of values where validJumps[index] has value 0 (default), 1 (jumpdest), 2 (beginsub) cachedPushes: { [pc: number]: bigint } stateManager: StateManagerInterface - blockchain: Blockchain + blockchain: EVMMockBlockchainInterface env: Env messageGasLimit?: bigint // Cache value from `gas.ts` to save gas limit for a message call interpreter: Interpreter @@ -147,7 +154,7 @@ export class Interpreter { constructor( evm: EVM, stateManager: StateManagerInterface, - blockchain: Blockchain, + blockchain: EVMMockBlockchainInterface, env: Env, gasLeft: bigint, journal: Journal, diff --git a/packages/evm/src/opcodes/codes.ts b/packages/evm/src/opcodes/codes.ts index f782c54560..42993e7853 100644 --- a/packages/evm/src/opcodes/codes.ts +++ b/packages/evm/src/opcodes/codes.ts @@ -52,159 +52,173 @@ export type OpcodeList = Map type OpcodeEntry = { [key: number]: { name: string; isAsync: boolean; dynamicGas: boolean } } type OpcodeEntryFee = OpcodeEntry & { [key: number]: { fee: number } } +// Default: sync and no dynamic gas +const defaultOp = (name: string) => { + return { name, isAsync: false, dynamicGas: false } +} +const dynamicGasOp = (name: string) => { + return { name, isAsync: false, dynamicGas: true } +} +const asyncOp = (name: string) => { + return { name, isAsync: true, dynamicGas: false } +} +const asyncAndDynamicGasOp = (name: string) => { + return { name, isAsync: true, dynamicGas: true } +} + // Base opcode list. The opcode list is extended in future hardforks const opcodes: OpcodeEntry = { // 0x0 range - arithmetic ops // name, async - 0x00: { name: 'STOP', isAsync: false, dynamicGas: false }, - 0x01: { name: 'ADD', isAsync: false, dynamicGas: false }, - 0x02: { name: 'MUL', isAsync: false, dynamicGas: false }, - 0x03: { name: 'SUB', isAsync: false, dynamicGas: false }, - 0x04: { name: 'DIV', isAsync: false, dynamicGas: false }, - 0x05: { name: 'SDIV', isAsync: false, dynamicGas: false }, - 0x06: { name: 'MOD', isAsync: false, dynamicGas: false }, - 0x07: { name: 'SMOD', isAsync: false, dynamicGas: false }, - 0x08: { name: 'ADDMOD', isAsync: false, dynamicGas: false }, - 0x09: { name: 'MULMOD', isAsync: false, dynamicGas: false }, - 0x0a: { name: 'EXP', isAsync: false, dynamicGas: true }, - 0x0b: { name: 'SIGNEXTEND', isAsync: false, dynamicGas: false }, + 0x00: defaultOp('STOP'), + 0x01: defaultOp('ADD'), + 0x02: defaultOp('MUL'), + 0x03: defaultOp('SUB'), + 0x04: defaultOp('DIV'), + 0x05: defaultOp('SDIV'), + 0x06: defaultOp('MOD'), + 0x07: defaultOp('SMOD'), + 0x08: defaultOp('ADDMOD'), + 0x09: defaultOp('MULMOD'), + 0x0a: dynamicGasOp('EXP'), + 0x0b: defaultOp('SIGNEXTEND'), // 0x10 range - bit ops - 0x10: { name: 'LT', isAsync: false, dynamicGas: false }, - 0x11: { name: 'GT', isAsync: false, dynamicGas: false }, - 0x12: { name: 'SLT', isAsync: false, dynamicGas: false }, - 0x13: { name: 'SGT', isAsync: false, dynamicGas: false }, - 0x14: { name: 'EQ', isAsync: false, dynamicGas: false }, - 0x15: { name: 'ISZERO', isAsync: false, dynamicGas: false }, - 0x16: { name: 'AND', isAsync: false, dynamicGas: false }, - 0x17: { name: 'OR', isAsync: false, dynamicGas: false }, - 0x18: { name: 'XOR', isAsync: false, dynamicGas: false }, - 0x19: { name: 'NOT', isAsync: false, dynamicGas: false }, - 0x1a: { name: 'BYTE', isAsync: false, dynamicGas: false }, + 0x10: defaultOp('LT'), + 0x11: defaultOp('GT'), + 0x12: defaultOp('SLT'), + 0x13: defaultOp('SGT'), + 0x14: defaultOp('EQ'), + 0x15: defaultOp('ISZERO'), + 0x16: defaultOp('AND'), + 0x17: defaultOp('OR'), + 0x18: defaultOp('XOR'), + 0x19: defaultOp('NOT'), + 0x1a: defaultOp('BYTE'), // 0x20 range - crypto - 0x20: { name: 'KECCAK256', isAsync: false, dynamicGas: true }, + 0x20: dynamicGasOp('KECCAK256'), // 0x30 range - closure state - 0x30: { name: 'ADDRESS', isAsync: true, dynamicGas: false }, - 0x31: { name: 'BALANCE', isAsync: true, dynamicGas: true }, - 0x32: { name: 'ORIGIN', isAsync: true, dynamicGas: false }, - 0x33: { name: 'CALLER', isAsync: true, dynamicGas: false }, - 0x34: { name: 'CALLVALUE', isAsync: true, dynamicGas: false }, - 0x35: { name: 'CALLDATALOAD', isAsync: true, dynamicGas: false }, - 0x36: { name: 'CALLDATASIZE', isAsync: true, dynamicGas: false }, - 0x37: { name: 'CALLDATACOPY', isAsync: true, dynamicGas: true }, - 0x38: { name: 'CODESIZE', isAsync: false, dynamicGas: false }, - 0x39: { name: 'CODECOPY', isAsync: false, dynamicGas: true }, - 0x3a: { name: 'GASPRICE', isAsync: false, dynamicGas: false }, - 0x3b: { name: 'EXTCODESIZE', isAsync: true, dynamicGas: true }, - 0x3c: { name: 'EXTCODECOPY', isAsync: true, dynamicGas: true }, + 0x30: asyncOp('ADDRESS'), + 0x31: asyncAndDynamicGasOp('BALANCE'), + 0x32: asyncOp('ORIGIN'), + 0x33: asyncOp('CALLER'), + 0x34: asyncOp('CALLVALUE'), + 0x35: asyncOp('CALLDATALOAD'), + 0x36: asyncOp('CALLDATASIZE'), + 0x37: asyncAndDynamicGasOp('CALLDATACOPY'), + 0x38: defaultOp('CODESIZE'), + 0x39: dynamicGasOp('CODECOPY'), + 0x3a: defaultOp('GASPRICE'), + 0x3b: asyncAndDynamicGasOp('EXTCODESIZE'), + 0x3c: asyncAndDynamicGasOp('EXTCODECOPY'), // '0x40' range - block operations - 0x40: { name: 'BLOCKHASH', isAsync: true, dynamicGas: false }, - 0x41: { name: 'COINBASE', isAsync: true, dynamicGas: false }, - 0x42: { name: 'TIMESTAMP', isAsync: true, dynamicGas: false }, - 0x43: { name: 'NUMBER', isAsync: true, dynamicGas: false }, - 0x44: { name: 'DIFFICULTY', isAsync: true, dynamicGas: false }, - 0x45: { name: 'GASLIMIT', isAsync: true, dynamicGas: false }, + 0x40: asyncOp('BLOCKHASH'), + 0x41: asyncOp('COINBASE'), + 0x42: asyncOp('TIMESTAMP'), + 0x43: asyncOp('NUMBER'), + 0x44: asyncOp('DIFFICULTY'), + 0x45: asyncOp('GASLIMIT'), // 0x50 range - 'storage' and execution - 0x50: { name: 'POP', isAsync: false, dynamicGas: false }, - 0x51: { name: 'MLOAD', isAsync: false, dynamicGas: true }, - 0x52: { name: 'MSTORE', isAsync: false, dynamicGas: true }, - 0x53: { name: 'MSTORE8', isAsync: false, dynamicGas: true }, - 0x54: { name: 'SLOAD', isAsync: true, dynamicGas: true }, - 0x55: { name: 'SSTORE', isAsync: true, dynamicGas: true }, - 0x56: { name: 'JUMP', isAsync: false, dynamicGas: false }, - 0x57: { name: 'JUMPI', isAsync: false, dynamicGas: false }, - 0x58: { name: 'PC', isAsync: false, dynamicGas: false }, - 0x59: { name: 'MSIZE', isAsync: false, dynamicGas: false }, - 0x5a: { name: 'GAS', isAsync: false, dynamicGas: false }, - 0x5b: { name: 'JUMPDEST', isAsync: false, dynamicGas: false }, + 0x50: defaultOp('POP'), + 0x51: dynamicGasOp('MLOAD'), + 0x52: dynamicGasOp('MSTORE'), + 0x53: dynamicGasOp('MSTORE8'), + 0x54: asyncAndDynamicGasOp('SLOAD'), + 0x55: asyncAndDynamicGasOp('SSTORE'), + 0x56: defaultOp('JUMP'), + 0x57: defaultOp('JUMPI'), + 0x58: defaultOp('PC'), + 0x59: defaultOp('MSIZE'), + 0x5a: defaultOp('GAS'), + 0x5b: defaultOp('JUMPDEST'), // 0x60, range - 0x60: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x61: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x62: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x63: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x64: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x65: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x66: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x67: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x68: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x69: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x6a: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x6b: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x6c: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x6d: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x6e: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x6f: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x70: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x71: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x72: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x73: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x74: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x75: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x76: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x77: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x78: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x79: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x7a: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x7b: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x7c: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x7d: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x7e: { name: 'PUSH', isAsync: false, dynamicGas: false }, - 0x7f: { name: 'PUSH', isAsync: false, dynamicGas: false }, - - 0x80: { name: 'DUP', isAsync: false, dynamicGas: false }, - 0x81: { name: 'DUP', isAsync: false, dynamicGas: false }, - 0x82: { name: 'DUP', isAsync: false, dynamicGas: false }, - 0x83: { name: 'DUP', isAsync: false, dynamicGas: false }, - 0x84: { name: 'DUP', isAsync: false, dynamicGas: false }, - 0x85: { name: 'DUP', isAsync: false, dynamicGas: false }, - 0x86: { name: 'DUP', isAsync: false, dynamicGas: false }, - 0x87: { name: 'DUP', isAsync: false, dynamicGas: false }, - 0x88: { name: 'DUP', isAsync: false, dynamicGas: false }, - 0x89: { name: 'DUP', isAsync: false, dynamicGas: false }, - 0x8a: { name: 'DUP', isAsync: false, dynamicGas: false }, - 0x8b: { name: 'DUP', isAsync: false, dynamicGas: false }, - 0x8c: { name: 'DUP', isAsync: false, dynamicGas: false }, - 0x8d: { name: 'DUP', isAsync: false, dynamicGas: false }, - 0x8e: { name: 'DUP', isAsync: false, dynamicGas: false }, - 0x8f: { name: 'DUP', isAsync: false, dynamicGas: false }, - - 0x90: { name: 'SWAP', isAsync: false, dynamicGas: false }, - 0x91: { name: 'SWAP', isAsync: false, dynamicGas: false }, - 0x92: { name: 'SWAP', isAsync: false, dynamicGas: false }, - 0x93: { name: 'SWAP', isAsync: false, dynamicGas: false }, - 0x94: { name: 'SWAP', isAsync: false, dynamicGas: false }, - 0x95: { name: 'SWAP', isAsync: false, dynamicGas: false }, - 0x96: { name: 'SWAP', isAsync: false, dynamicGas: false }, - 0x97: { name: 'SWAP', isAsync: false, dynamicGas: false }, - 0x98: { name: 'SWAP', isAsync: false, dynamicGas: false }, - 0x99: { name: 'SWAP', isAsync: false, dynamicGas: false }, - 0x9a: { name: 'SWAP', isAsync: false, dynamicGas: false }, - 0x9b: { name: 'SWAP', isAsync: false, dynamicGas: false }, - 0x9c: { name: 'SWAP', isAsync: false, dynamicGas: false }, - 0x9d: { name: 'SWAP', isAsync: false, dynamicGas: false }, - 0x9e: { name: 'SWAP', isAsync: false, dynamicGas: false }, - 0x9f: { name: 'SWAP', isAsync: false, dynamicGas: false }, - - 0xa0: { name: 'LOG', isAsync: false, dynamicGas: true }, - 0xa1: { name: 'LOG', isAsync: false, dynamicGas: true }, - 0xa2: { name: 'LOG', isAsync: false, dynamicGas: true }, - 0xa3: { name: 'LOG', isAsync: false, dynamicGas: true }, - 0xa4: { name: 'LOG', isAsync: false, dynamicGas: true }, + 0x60: defaultOp('PUSH'), + 0x61: defaultOp('PUSH'), + 0x62: defaultOp('PUSH'), + 0x63: defaultOp('PUSH'), + 0x64: defaultOp('PUSH'), + 0x65: defaultOp('PUSH'), + 0x66: defaultOp('PUSH'), + 0x67: defaultOp('PUSH'), + 0x68: defaultOp('PUSH'), + 0x69: defaultOp('PUSH'), + 0x6a: defaultOp('PUSH'), + 0x6b: defaultOp('PUSH'), + 0x6c: defaultOp('PUSH'), + 0x6d: defaultOp('PUSH'), + 0x6e: defaultOp('PUSH'), + 0x6f: defaultOp('PUSH'), + 0x70: defaultOp('PUSH'), + 0x71: defaultOp('PUSH'), + 0x72: defaultOp('PUSH'), + 0x73: defaultOp('PUSH'), + 0x74: defaultOp('PUSH'), + 0x75: defaultOp('PUSH'), + 0x76: defaultOp('PUSH'), + 0x77: defaultOp('PUSH'), + 0x78: defaultOp('PUSH'), + 0x79: defaultOp('PUSH'), + 0x7a: defaultOp('PUSH'), + 0x7b: defaultOp('PUSH'), + 0x7c: defaultOp('PUSH'), + 0x7d: defaultOp('PUSH'), + 0x7e: defaultOp('PUSH'), + 0x7f: defaultOp('PUSH'), + + 0x80: defaultOp('DUP'), + 0x81: defaultOp('DUP'), + 0x82: defaultOp('DUP'), + 0x83: defaultOp('DUP'), + 0x84: defaultOp('DUP'), + 0x85: defaultOp('DUP'), + 0x86: defaultOp('DUP'), + 0x87: defaultOp('DUP'), + 0x88: defaultOp('DUP'), + 0x89: defaultOp('DUP'), + 0x8a: defaultOp('DUP'), + 0x8b: defaultOp('DUP'), + 0x8c: defaultOp('DUP'), + 0x8d: defaultOp('DUP'), + 0x8e: defaultOp('DUP'), + 0x8f: defaultOp('DUP'), + + 0x90: defaultOp('SWAP'), + 0x91: defaultOp('SWAP'), + 0x92: defaultOp('SWAP'), + 0x93: defaultOp('SWAP'), + 0x94: defaultOp('SWAP'), + 0x95: defaultOp('SWAP'), + 0x96: defaultOp('SWAP'), + 0x97: defaultOp('SWAP'), + 0x98: defaultOp('SWAP'), + 0x99: defaultOp('SWAP'), + 0x9a: defaultOp('SWAP'), + 0x9b: defaultOp('SWAP'), + 0x9c: defaultOp('SWAP'), + 0x9d: defaultOp('SWAP'), + 0x9e: defaultOp('SWAP'), + 0x9f: defaultOp('SWAP'), + + 0xa0: dynamicGasOp('LOG'), + 0xa1: dynamicGasOp('LOG'), + 0xa2: dynamicGasOp('LOG'), + 0xa3: dynamicGasOp('LOG'), + 0xa4: dynamicGasOp('LOG'), // '0xf0' range - closures - 0xf0: { name: 'CREATE', isAsync: true, dynamicGas: true }, - 0xf1: { name: 'CALL', isAsync: true, dynamicGas: true }, - 0xf2: { name: 'CALLCODE', isAsync: true, dynamicGas: true }, - 0xf3: { name: 'RETURN', isAsync: false, dynamicGas: true }, + 0xf0: asyncAndDynamicGasOp('CREATE'), + 0xf1: asyncAndDynamicGasOp('CALL'), + 0xf2: asyncAndDynamicGasOp('CALLCODE'), + 0xf3: dynamicGasOp('RETURN'), // '0x70', range - other - 0xfe: { name: 'INVALID', isAsync: false, dynamicGas: false }, - 0xff: { name: 'SELFDESTRUCT', isAsync: true, dynamicGas: true }, + 0xfe: defaultOp('INVALID'), + 0xff: asyncAndDynamicGasOp('SELFDESTRUCT'), } // Array of hard forks in order. These changes are repeatedly applied to `opcodes` until the hard fork is in the future based upon the common @@ -216,52 +230,52 @@ const hardforkOpcodes: { hardfork: Hardfork; opcodes: OpcodeEntry }[] = [ { hardfork: Hardfork.Homestead, opcodes: { - 0xf4: { name: 'DELEGATECALL', isAsync: true, dynamicGas: true }, // EIP-7 + 0xf4: asyncAndDynamicGasOp('DELEGATECALL'), // EIP-7 }, }, { hardfork: Hardfork.TangerineWhistle, opcodes: { - 0x54: { name: 'SLOAD', isAsync: true, dynamicGas: true }, - 0xf1: { name: 'CALL', isAsync: true, dynamicGas: true }, - 0xf2: { name: 'CALLCODE', isAsync: true, dynamicGas: true }, - 0x3b: { name: 'EXTCODESIZE', isAsync: true, dynamicGas: true }, - 0x3c: { name: 'EXTCODECOPY', isAsync: true, dynamicGas: true }, - 0xf4: { name: 'DELEGATECALL', isAsync: true, dynamicGas: true }, // EIP-7 - 0xff: { name: 'SELFDESTRUCT', isAsync: true, dynamicGas: true }, - 0x31: { name: 'BALANCE', isAsync: true, dynamicGas: true }, + 0x54: asyncAndDynamicGasOp('SLOAD'), + 0xf1: asyncAndDynamicGasOp('CALL'), + 0xf2: asyncAndDynamicGasOp('CALLCODE'), + 0x3b: asyncAndDynamicGasOp('EXTCODESIZE'), + 0x3c: asyncAndDynamicGasOp('EXTCODECOPY'), + 0xf4: asyncAndDynamicGasOp('DELEGATECALL'), // EIP-7 + 0xff: asyncAndDynamicGasOp('SELFDESTRUCT'), + 0x31: asyncAndDynamicGasOp('BALANCE'), }, }, { hardfork: Hardfork.Byzantium, opcodes: { - 0xfd: { name: 'REVERT', isAsync: false, dynamicGas: true }, // EIP-140 - 0xfa: { name: 'STATICCALL', isAsync: true, dynamicGas: true }, // EIP-214 - 0x3d: { name: 'RETURNDATASIZE', isAsync: true, dynamicGas: false }, // EIP-211 - 0x3e: { name: 'RETURNDATACOPY', isAsync: true, dynamicGas: true }, // EIP-211 + 0xfd: dynamicGasOp('REVERT'), // EIP-140 + 0xfa: asyncAndDynamicGasOp('STATICCALL'), // EIP-214 + 0x3d: asyncOp('RETURNDATASIZE'), // EIP-211 + 0x3e: asyncAndDynamicGasOp('RETURNDATACOPY'), // EIP-211 }, }, { hardfork: Hardfork.Constantinople, opcodes: { - 0x1b: { name: 'SHL', isAsync: false, dynamicGas: false }, // EIP-145 - 0x1c: { name: 'SHR', isAsync: false, dynamicGas: false }, // EIP-145 - 0x1d: { name: 'SAR', isAsync: false, dynamicGas: false }, // EIP-145 - 0x3f: { name: 'EXTCODEHASH', isAsync: true, dynamicGas: true }, // EIP-1052 - 0xf5: { name: 'CREATE2', isAsync: true, dynamicGas: true }, // EIP-1014 + 0x1b: defaultOp('SHL'), // EIP-145 + 0x1c: defaultOp('SHR'), // EIP-145 + 0x1d: defaultOp('SAR'), // EIP-145 + 0x3f: asyncAndDynamicGasOp('EXTCODEHASH'), // EIP-1052 + 0xf5: asyncAndDynamicGasOp('CREATE2'), // EIP-1014 }, }, { hardfork: Hardfork.Istanbul, opcodes: { - 0x46: { name: 'CHAINID', isAsync: false, dynamicGas: false }, // EIP-1344 - 0x47: { name: 'SELFBALANCE', isAsync: false, dynamicGas: false }, // EIP-1884 + 0x46: defaultOp('CHAINID'), // EIP-1344 + 0x47: defaultOp('SELFBALANCE'), // EIP-1884 }, }, { hardfork: Hardfork.Paris, opcodes: { - 0x44: { name: 'PREVRANDAO', isAsync: true, dynamicGas: false }, // EIP-4399 + 0x44: asyncOp('PREVRANDAO'), // EIP-4399 }, }, ] @@ -270,92 +284,92 @@ const eipOpcodes: { eip: number; opcodes: OpcodeEntry }[] = [ { eip: 663, opcodes: { - 0xe6: { name: 'DUPN', isAsync: false, dynamicGas: false }, - 0xe7: { name: 'SWAPN', isAsync: false, dynamicGas: false }, - 0xe8: { name: 'EXCHANGE', isAsync: false, dynamicGas: false }, + 0xe6: defaultOp('DUPN'), + 0xe7: defaultOp('SWAPN'), + 0xe8: defaultOp('EXCHANGE'), }, }, { eip: 1153, opcodes: { - 0x5c: { name: 'TLOAD', isAsync: false, dynamicGas: false }, - 0x5d: { name: 'TSTORE', isAsync: false, dynamicGas: false }, + 0x5c: defaultOp('TLOAD'), + 0x5d: defaultOp('TSTORE'), }, }, { eip: 3198, opcodes: { - 0x48: { name: 'BASEFEE', isAsync: false, dynamicGas: false }, + 0x48: defaultOp('BASEFEE'), }, }, { eip: 3855, opcodes: { - 0x5f: { name: 'PUSH0', isAsync: false, dynamicGas: false }, + 0x5f: defaultOp('PUSH0'), }, }, { eip: 4200, opcodes: { - 0xe0: { name: 'RJUMP', isAsync: false, dynamicGas: false }, - 0xe1: { name: 'RJUMPI', isAsync: false, dynamicGas: false }, - 0xe2: { name: 'RJUMPV', isAsync: false, dynamicGas: false }, + 0xe0: defaultOp('RJUMP'), + 0xe1: defaultOp('RJUMPI'), + 0xe2: defaultOp('RJUMPV'), }, }, { eip: 4750, opcodes: { - 0xe3: { name: 'CALLF', isAsync: false, dynamicGas: false }, - 0xe4: { name: 'RETF', isAsync: false, dynamicGas: false }, + 0xe3: defaultOp('CALLF'), + 0xe4: defaultOp('RETF'), }, }, { eip: 4844, opcodes: { - 0x49: { name: 'BLOBHASH', isAsync: false, dynamicGas: false }, + 0x49: defaultOp('BLOBHASH'), }, }, { eip: 5656, opcodes: { - 0x5e: { name: 'MCOPY', isAsync: false, dynamicGas: true }, + 0x5e: dynamicGasOp('MCOPY'), }, }, { eip: 6206, opcodes: { - 0xe5: { name: 'JUMPF', isAsync: false, dynamicGas: false }, + 0xe5: defaultOp('JUMPF'), }, }, { eip: 7069, opcodes: { - 0xf7: { name: 'RETURNDATALOAD', isAsync: false, dynamicGas: false }, - 0xf8: { name: 'EXTCALL', isAsync: true, dynamicGas: true }, - 0xf9: { name: 'EXTDELEGATECALL', isAsync: true, dynamicGas: true }, - 0xfb: { name: 'EXTSTATICCALL', isAsync: true, dynamicGas: true }, + 0xf7: defaultOp('RETURNDATALOAD'), + 0xf8: asyncAndDynamicGasOp('EXTCALL'), + 0xf9: asyncAndDynamicGasOp('EXTDELEGATECALL'), + 0xfb: asyncAndDynamicGasOp('EXTSTATICCALL'), }, }, { eip: 7480, opcodes: { - 0xd0: { name: 'DATALOAD', isAsync: false, dynamicGas: false }, - 0xd1: { name: 'DATALOADN', isAsync: false, dynamicGas: false }, - 0xd2: { name: 'DATASIZE', isAsync: false, dynamicGas: false }, - 0xd3: { name: 'DATACOPY', isAsync: false, dynamicGas: true }, + 0xd0: defaultOp('DATALOAD'), + 0xd1: defaultOp('DATALOADN'), + 0xd2: defaultOp('DATASIZE'), + 0xd3: dynamicGasOp('DATACOPY'), }, }, { eip: 7516, opcodes: { - 0x4a: { name: 'BLOBBASEFEE', isAsync: false, dynamicGas: false }, + 0x4a: defaultOp('BLOBBASEFEE'), }, }, { eip: 7620, opcodes: { - 0xec: { name: 'EOFCREATE', isAsync: true, dynamicGas: true }, - 0xee: { name: 'RETURNCONTRACT', isAsync: true, dynamicGas: true }, + 0xec: asyncAndDynamicGasOp('EOFCREATE'), + 0xee: asyncAndDynamicGasOp('RETURNCONTRACT'), }, }, ] diff --git a/packages/evm/src/opcodes/functions.ts b/packages/evm/src/opcodes/functions.ts index 0d29b62bcc..a79204eb98 100644 --- a/packages/evm/src/opcodes/functions.ts +++ b/packages/evm/src/opcodes/functions.ts @@ -60,9 +60,15 @@ export interface AsyncOpHandler { export type OpHandler = SyncOpHandler | AsyncOpHandler -async function eip7702CodeCheck(runState: RunState, code: Uint8Array) { +function getEIP7702DelegatedAddress(code: Uint8Array) { if (equalBytes(code.slice(0, 3), new Uint8Array([0xef, 0x01, 0x00]))) { - const address = new Address(code.slice(3, 24)) + return new Address(code.slice(3, 24)) + } +} + +async function eip7702CodeCheck(runState: RunState, code: Uint8Array) { + const address = getEIP7702DelegatedAddress(code) + if (address !== undefined) { return runState.stateManager.getCode(address) } @@ -571,16 +577,26 @@ export const handlers: Map = new Map([ const address = createAddressFromStackBigInt(addressBigInt) // EOF check - let code = await runState.stateManager.getCode(address) + const code = await runState.stateManager.getCode(address) if (isEOF(code)) { // In legacy code, the target code is treated as to be "EOFBYTES" code // Therefore, push the hash of EOFBYTES to the stack runState.stack.push(bytesToBigInt(EOFHASH)) return } else if (common.isActivatedEIP(7702)) { - code = await eip7702CodeCheck(runState, code) - runState.stack.push(bytesToBigInt(keccak256(code))) - return + const possibleDelegatedAddress = getEIP7702DelegatedAddress(code) + if (possibleDelegatedAddress !== undefined) { + const account = await runState.stateManager.getAccount(possibleDelegatedAddress) + if (!account || account.isEmpty()) { + runState.stack.push(BIGINT_0) + return + } + + runState.stack.push(BigInt(bytesToHex(account.codeHash))) + } else { + runState.stack.push(bytesToBigInt(keccak256(code))) + return + } } const account = await runState.stateManager.getAccount(address) diff --git a/packages/evm/src/precompiles/01-ecrecover.ts b/packages/evm/src/precompiles/01-ecrecover.ts index e395d17b78..79bad3c941 100644 --- a/packages/evm/src/precompiles/01-ecrecover.ts +++ b/packages/evm/src/precompiles/01-ecrecover.ts @@ -7,29 +7,19 @@ import { publicToAddress, setLengthLeft, setLengthRight, - short, } from '@ethereumjs/util' import { OOGResult } from '../evm.js' +import { gasLimitCheck } from './util.js' + import type { ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' export function precompile01(opts: PrecompileInput): ExecResult { const ecrecoverFunction = opts.common.customCrypto.ecrecover ?? ecrecover const gasUsed = opts.common.param('ecRecoverGas') - if (opts._debug !== undefined) { - opts._debug( - `Run ECRECOVER (0x01) precompile data=${short(opts.data)} length=${ - opts.data.length - } gasLimit=${opts.gasLimit} gasUsed=${gasUsed}`, - ) - } - - if (opts.gasLimit < gasUsed) { - if (opts._debug !== undefined) { - opts._debug(`ECRECOVER (0x01) failed: OOG`) - } + if (!gasLimitCheck(opts, gasUsed, 'ECRECOVER (0x01)')) { return OOGResult(opts.gasLimit) } diff --git a/packages/evm/src/precompiles/02-sha256.ts b/packages/evm/src/precompiles/02-sha256.ts index f6b1240885..ebfa2c9ebc 100644 --- a/packages/evm/src/precompiles/02-sha256.ts +++ b/packages/evm/src/precompiles/02-sha256.ts @@ -1,8 +1,10 @@ -import { bytesToHex, short } from '@ethereumjs/util' +import { bytesToHex } from '@ethereumjs/util' import { sha256 } from 'ethereum-cryptography/sha256.js' import { OOGResult } from '../evm.js' +import { gasLimitCheck } from './util.js' + import type { ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' @@ -12,18 +14,7 @@ export function precompile02(opts: PrecompileInput): ExecResult { let gasUsed = opts.common.param('sha256Gas') gasUsed += opts.common.param('sha256WordGas') * BigInt(Math.ceil(data.length / 32)) - if (opts._debug !== undefined) { - opts._debug( - `Run KECCAK256 (0x02) precompile data=${short(opts.data)} length=${ - opts.data.length - } gasLimit=${opts.gasLimit} gasUsed=${gasUsed}`, - ) - } - - if (opts.gasLimit < gasUsed) { - if (opts._debug !== undefined) { - opts._debug(`KECCAK256 (0x02) failed: OOG`) - } + if (!gasLimitCheck(opts, gasUsed, 'KECCAK256 (0x02)')) { return OOGResult(opts.gasLimit) } diff --git a/packages/evm/src/precompiles/03-ripemd160.ts b/packages/evm/src/precompiles/03-ripemd160.ts index 366508ae0d..e4e7233751 100644 --- a/packages/evm/src/precompiles/03-ripemd160.ts +++ b/packages/evm/src/precompiles/03-ripemd160.ts @@ -1,8 +1,10 @@ -import { bytesToHex, setLengthLeft, short } from '@ethereumjs/util' +import { bytesToHex, setLengthLeft } from '@ethereumjs/util' import { ripemd160 } from 'ethereum-cryptography/ripemd160.js' import { OOGResult } from '../evm.js' +import { gasLimitCheck } from './util.js' + import type { ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' @@ -12,18 +14,7 @@ export function precompile03(opts: PrecompileInput): ExecResult { let gasUsed = opts.common.param('ripemd160Gas') gasUsed += opts.common.param('ripemd160WordGas') * BigInt(Math.ceil(data.length / 32)) - if (opts._debug !== undefined) { - opts._debug( - `Run RIPEMD160 (0x03) precompile data=${short(opts.data)} length=${ - opts.data.length - } gasLimit=${opts.gasLimit} gasUsed=${gasUsed}`, - ) - } - - if (opts.gasLimit < gasUsed) { - if (opts._debug !== undefined) { - opts._debug(`RIPEMD160 (0x03) failed: OOG`) - } + if (!gasLimitCheck(opts, gasUsed, 'RIPEMD160 (0x03)')) { return OOGResult(opts.gasLimit) } diff --git a/packages/evm/src/precompiles/04-identity.ts b/packages/evm/src/precompiles/04-identity.ts index b06d5357f5..d2a5f70057 100644 --- a/packages/evm/src/precompiles/04-identity.ts +++ b/packages/evm/src/precompiles/04-identity.ts @@ -2,6 +2,8 @@ import { short } from '@ethereumjs/util' import { OOGResult } from '../evm.js' +import { gasLimitCheck } from './util.js' + import type { ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' @@ -10,18 +12,7 @@ export function precompile04(opts: PrecompileInput): ExecResult { let gasUsed = opts.common.param('identityGas') gasUsed += opts.common.param('identityWordGas') * BigInt(Math.ceil(data.length / 32)) - if (opts._debug !== undefined) { - opts._debug( - `Run IDENTITY (0x04) precompile data=${short(opts.data)} length=${ - opts.data.length - } gasLimit=${opts.gasLimit} gasUsed=${gasUsed}`, - ) - } - - if (opts.gasLimit < gasUsed) { - if (opts._debug !== undefined) { - opts._debug(`IDENTITY (0x04) failed: OOG`) - } + if (!gasLimitCheck(opts, gasUsed, 'IDENTITY (0x04)')) { return OOGResult(opts.gasLimit) } diff --git a/packages/evm/src/precompiles/05-modexp.ts b/packages/evm/src/precompiles/05-modexp.ts index c472a19fed..a1f24b7413 100644 --- a/packages/evm/src/precompiles/05-modexp.ts +++ b/packages/evm/src/precompiles/05-modexp.ts @@ -12,11 +12,12 @@ import { bytesToHex, setLengthLeft, setLengthRight, - short, } from '@ethereumjs/util' import { OOGResult } from '../evm.js' +import { gasLimitCheck } from './util.js' + import type { ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' @@ -136,18 +137,7 @@ export function precompile05(opts: PrecompileInput): ExecResult { gasUsed = BIGINT_200 } } - if (opts._debug !== undefined) { - opts._debug( - `Run MODEXP (0x05) precompile data=${short(opts.data)} length=${opts.data.length} gasLimit=${ - opts.gasLimit - } gasUsed=${gasUsed}`, - ) - } - - if (opts.gasLimit < gasUsed) { - if (opts._debug !== undefined) { - opts._debug(`MODEXP (0x05) failed: OOG`) - } + if (!gasLimitCheck(opts, gasUsed, 'MODEXP (0x05)')) { return OOGResult(opts.gasLimit) } diff --git a/packages/evm/src/precompiles/06-ecadd.ts b/packages/evm/src/precompiles/06-ecadd.ts index 0c498f15f1..b86e9eafb8 100644 --- a/packages/evm/src/precompiles/06-ecadd.ts +++ b/packages/evm/src/precompiles/06-ecadd.ts @@ -1,24 +1,16 @@ -import { bytesToHex, setLengthRight, short } from '@ethereumjs/util' +import { bytesToHex, setLengthRight } from '@ethereumjs/util' import { EvmErrorResult, OOGResult } from '../evm.js' +import { gasLimitCheck } from './util.js' + import type { EVM } from '../evm.js' import type { ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' export function precompile06(opts: PrecompileInput): ExecResult { const gasUsed = opts.common.param('ecAddGas') - if (opts._debug !== undefined) { - opts._debug( - `Run ECADD (0x06) precompile data=${short(opts.data)} length=${opts.data.length} gasLimit=${ - opts.gasLimit - } gasUsed=${gasUsed}`, - ) - } - if (opts.gasLimit < gasUsed) { - if (opts._debug !== undefined) { - opts._debug(`ECADD (0x06) failed: OOG`) - } + if (!gasLimitCheck(opts, gasUsed, 'ECADD (0x06)')) { return OOGResult(opts.gasLimit) } diff --git a/packages/evm/src/precompiles/07-ecmul.ts b/packages/evm/src/precompiles/07-ecmul.ts index a1d3dda857..34319642ed 100644 --- a/packages/evm/src/precompiles/07-ecmul.ts +++ b/packages/evm/src/precompiles/07-ecmul.ts @@ -1,25 +1,16 @@ -import { bytesToHex, setLengthRight, short } from '@ethereumjs/util' +import { bytesToHex, setLengthRight } from '@ethereumjs/util' import { EvmErrorResult, OOGResult } from '../evm.js' +import { gasLimitCheck } from './util.js' + import type { EVM } from '../evm.js' import type { ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' export function precompile07(opts: PrecompileInput): ExecResult { const gasUsed = opts.common.param('ecMulGas') - if (opts._debug !== undefined) { - opts._debug( - `Run ECMUL (0x07) precompile data=${short(opts.data)} length=${opts.data.length} gasLimit=${ - opts.gasLimit - } gasUsed=${gasUsed}`, - ) - } - - if (opts.gasLimit < gasUsed) { - if (opts._debug !== undefined) { - opts._debug(`ECMUL (0x07) failed: OOG`) - } + if (!gasLimitCheck(opts, gasUsed, 'ECMUL (0x07)')) { return OOGResult(opts.gasLimit) } diff --git a/packages/evm/src/precompiles/08-ecpairing.ts b/packages/evm/src/precompiles/08-ecpairing.ts index 3715d5082b..0dcdc25561 100644 --- a/packages/evm/src/precompiles/08-ecpairing.ts +++ b/packages/evm/src/precompiles/08-ecpairing.ts @@ -1,9 +1,9 @@ -import { bytesToHex, short } from '@ethereumjs/util' +import { bytesToHex } from '@ethereumjs/util' import { EvmErrorResult, OOGResult } from '../evm.js' import { ERROR, EvmError } from '../exceptions.js' -import { moduloLengthCheck } from './util.js' +import { gasLimitCheck, moduloLengthCheck } from './util.js' import type { EVM } from '../evm.js' import type { ExecResult } from '../types.js' @@ -17,18 +17,8 @@ export function precompile08(opts: PrecompileInput): ExecResult { const inputDataSize = BigInt(Math.floor(opts.data.length / 192)) const gasUsed = opts.common.param('ecPairingGas') + inputDataSize * opts.common.param('ecPairingWordGas') - if (opts._debug !== undefined) { - opts._debug( - `Run ECPAIRING (0x08) precompile data=${short(opts.data)} length=${ - opts.data.length - } gasLimit=${opts.gasLimit} gasUsed=${gasUsed}`, - ) - } - if (opts.gasLimit < gasUsed) { - if (opts._debug !== undefined) { - opts._debug(`ECPAIRING (0x08) failed: OOG`) - } + if (!gasLimitCheck(opts, gasUsed, 'ECPAIRING (0x08)')) { return OOGResult(opts.gasLimit) } diff --git a/packages/evm/src/precompiles/09-blake2f.ts b/packages/evm/src/precompiles/09-blake2f.ts index 8fce5e8c0c..a45fbd9a77 100644 --- a/packages/evm/src/precompiles/09-blake2f.ts +++ b/packages/evm/src/precompiles/09-blake2f.ts @@ -1,8 +1,10 @@ -import { bytesToHex, short } from '@ethereumjs/util' +import { bytesToHex } from '@ethereumjs/util' import { OOGResult } from '../evm.js' import { ERROR, EvmError } from '../exceptions.js' +import { gasLimitCheck } from './util.js' + import type { ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' @@ -190,18 +192,7 @@ export function precompile09(opts: PrecompileInput): ExecResult { let gasUsed = opts.common.param('blake2RoundGas') gasUsed *= BigInt(rounds) - if (opts._debug !== undefined) { - opts._debug( - `Run BLAKE2F (0x09) precompile data=${short(opts.data)} length=${opts.data.length} gasLimit=${ - opts.gasLimit - } gasUsed=${gasUsed}`, - ) - } - - if (opts.gasLimit < gasUsed) { - if (opts._debug !== undefined) { - opts._debug(`BLAKE2F (0x09) failed: OOG`) - } + if (!gasLimitCheck(opts, gasUsed, 'BLAKE2F (0x09)')) { return OOGResult(opts.gasLimit) } diff --git a/packages/evm/src/precompiles/0a-kzg-point-evaluation.ts b/packages/evm/src/precompiles/0a-kzg-point-evaluation.ts index e5ff2a5074..ceda3f7bcb 100644 --- a/packages/evm/src/precompiles/0a-kzg-point-evaluation.ts +++ b/packages/evm/src/precompiles/0a-kzg-point-evaluation.ts @@ -4,12 +4,13 @@ import { computeVersionedHash, concatBytes, setLengthLeft, - short, } from '@ethereumjs/util' import { EvmErrorResult, OOGResult } from '../evm.js' import { ERROR, EvmError } from '../exceptions.js' +import { gasLimitCheck } from './util.js' + import type { ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' @@ -24,18 +25,7 @@ export async function precompile0a(opts: PrecompileInput): Promise { throw new Error('kzg not initialized') } const gasUsed = opts.common.param('kzgPointEvaluationPrecompileGas') - if (opts._debug !== undefined) { - opts._debug( - `Run KZG_POINT_EVALUATION (0x14) precompile data=${short(opts.data)} length=${ - opts.data.length - } gasLimit=${opts.gasLimit} gasUsed=${gasUsed}`, - ) - } - - if (opts.gasLimit < gasUsed) { - if (opts._debug !== undefined) { - opts._debug(`KZG_POINT_EVALUATION (0x14) failed: OOG`) - } + if (!gasLimitCheck(opts, gasUsed, 'KZG_POINT_EVALUATION (0x14)')) { return OOGResult(opts.gasLimit) } diff --git a/packages/evm/src/precompiles/0b-bls12-g1add.ts b/packages/evm/src/precompiles/0b-bls12-g1add.ts index 34be7c5309..a585b86eac 100644 --- a/packages/evm/src/precompiles/0b-bls12-g1add.ts +++ b/packages/evm/src/precompiles/0b-bls12-g1add.ts @@ -3,8 +3,8 @@ import { bytesToHex } from '@ethereumjs/util' import { EvmErrorResult, OOGResult } from '../evm.js' import { ERROR, EvmError } from '../exceptions.js' -import { gasCheck, leading16ZeroBytesCheck } from './bls12_381/index.js' -import { equalityLengthCheck } from './util.js' +import { leading16ZeroBytesCheck } from './bls12_381/index.js' +import { equalityLengthCheck, gasLimitCheck } from './util.js' import type { EVMBLSInterface, ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' @@ -14,7 +14,7 @@ export async function precompile0b(opts: PrecompileInput): Promise { // note: the gas used is constant; even if the input is incorrect. const gasUsed = opts.common.paramByEIP('Bls12381G1AddGas', 2537) ?? BigInt(0) - if (!gasCheck(opts, gasUsed, 'BLS12G1ADD (0x0b)')) { + if (!gasLimitCheck(opts, gasUsed, 'BLS12G1ADD (0x0b)')) { return OOGResult(opts.gasLimit) } diff --git a/packages/evm/src/precompiles/0c-bls12-g1mul.ts b/packages/evm/src/precompiles/0c-bls12-g1mul.ts index 92dd9ab8ab..891aa7c3eb 100644 --- a/packages/evm/src/precompiles/0c-bls12-g1mul.ts +++ b/packages/evm/src/precompiles/0c-bls12-g1mul.ts @@ -3,8 +3,8 @@ import { bytesToHex } from '@ethereumjs/util' import { EvmErrorResult, OOGResult } from '../evm.js' import { ERROR, EvmError } from '../exceptions.js' -import { gasCheck, leading16ZeroBytesCheck } from './bls12_381/index.js' -import { equalityLengthCheck } from './util.js' +import { leading16ZeroBytesCheck } from './bls12_381/index.js' +import { equalityLengthCheck, gasLimitCheck } from './util.js' import type { EVMBLSInterface, ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' @@ -14,7 +14,7 @@ export async function precompile0c(opts: PrecompileInput): Promise { // note: the gas used is constant; even if the input is incorrect. const gasUsed = opts.common.paramByEIP('Bls12381G1MulGas', 2537) ?? BigInt(0) - if (!gasCheck(opts, gasUsed, 'BLS12G1MUL (0x0c)')) { + if (!gasLimitCheck(opts, gasUsed, 'BLS12G1MUL (0x0c)')) { return OOGResult(opts.gasLimit) } diff --git a/packages/evm/src/precompiles/0d-bls12-g1msm.ts b/packages/evm/src/precompiles/0d-bls12-g1msm.ts index 44e667f693..0ff151754c 100644 --- a/packages/evm/src/precompiles/0d-bls12-g1msm.ts +++ b/packages/evm/src/precompiles/0d-bls12-g1msm.ts @@ -3,8 +3,8 @@ import { bytesToHex } from '@ethereumjs/util' import { EvmErrorResult, OOGResult } from '../evm.js' import { ERROR, EvmError } from '../exceptions.js' -import { gasCheck, leading16ZeroBytesCheck, msmGasUsed } from './bls12_381/index.js' -import { moduloLengthCheck } from './util.js' +import { leading16ZeroBytesCheck, msmGasUsed } from './bls12_381/index.js' +import { gasLimitCheck, moduloLengthCheck } from './util.js' import type { EVMBLSInterface, ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' @@ -28,7 +28,7 @@ export async function precompile0d(opts: PrecompileInput): Promise { const gasUsedPerPair = opts.common.paramByEIP('Bls12381G1MulGas', 2537) ?? BigInt(0) const gasUsed = msmGasUsed(numPairs, gasUsedPerPair) - if (!gasCheck(opts, gasUsed, 'BLS12G1MSM (0x0d)')) { + if (!gasLimitCheck(opts, gasUsed, 'BLS12G1MSM (0x0d)')) { return OOGResult(opts.gasLimit) } diff --git a/packages/evm/src/precompiles/0e-bls12-g2add.ts b/packages/evm/src/precompiles/0e-bls12-g2add.ts index 019f815075..e9a6082917 100644 --- a/packages/evm/src/precompiles/0e-bls12-g2add.ts +++ b/packages/evm/src/precompiles/0e-bls12-g2add.ts @@ -3,8 +3,8 @@ import { bytesToHex } from '@ethereumjs/util' import { EvmErrorResult, OOGResult } from '../evm.js' import { ERROR, EvmError } from '../exceptions.js' -import { gasCheck, leading16ZeroBytesCheck } from './bls12_381/index.js' -import { equalityLengthCheck } from './util.js' +import { leading16ZeroBytesCheck } from './bls12_381/index.js' +import { equalityLengthCheck, gasLimitCheck } from './util.js' import type { EVMBLSInterface, ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' @@ -14,7 +14,7 @@ export async function precompile0e(opts: PrecompileInput): Promise { // note: the gas used is constant; even if the input is incorrect. const gasUsed = opts.common.paramByEIP('Bls12381G2AddGas', 2537) ?? BigInt(0) - if (!gasCheck(opts, gasUsed, 'BLS12G2ADD (0x0e)')) { + if (!gasLimitCheck(opts, gasUsed, 'BLS12G2ADD (0x0e)')) { return OOGResult(opts.gasLimit) } diff --git a/packages/evm/src/precompiles/0f-bls12-g2mul.ts b/packages/evm/src/precompiles/0f-bls12-g2mul.ts index a8817e0a65..4b8ed8c581 100644 --- a/packages/evm/src/precompiles/0f-bls12-g2mul.ts +++ b/packages/evm/src/precompiles/0f-bls12-g2mul.ts @@ -3,8 +3,8 @@ import { bytesToHex } from '@ethereumjs/util' import { EvmErrorResult, OOGResult } from '../evm.js' import { ERROR, EvmError } from '../exceptions.js' -import { gasCheck, leading16ZeroBytesCheck } from './bls12_381/index.js' -import { equalityLengthCheck } from './util.js' +import { leading16ZeroBytesCheck } from './bls12_381/index.js' +import { equalityLengthCheck, gasLimitCheck } from './util.js' import type { EVMBLSInterface, ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' @@ -14,7 +14,7 @@ export async function precompile0f(opts: PrecompileInput): Promise { // note: the gas used is constant; even if the input is incorrect. const gasUsed = opts.common.paramByEIP('Bls12381G2MulGas', 2537) ?? BigInt(0) - if (!gasCheck(opts, gasUsed, 'BLS12G2MUL (0x0f)')) { + if (!gasLimitCheck(opts, gasUsed, 'BLS12G2MUL (0x0f)')) { return OOGResult(opts.gasLimit) } diff --git a/packages/evm/src/precompiles/10-bls12-g2msm.ts b/packages/evm/src/precompiles/10-bls12-g2msm.ts index d62d0d0c52..d6bb66e208 100644 --- a/packages/evm/src/precompiles/10-bls12-g2msm.ts +++ b/packages/evm/src/precompiles/10-bls12-g2msm.ts @@ -3,8 +3,8 @@ import { bytesToHex } from '@ethereumjs/util' import { EvmErrorResult, OOGResult } from '../evm.js' import { ERROR, EvmError } from '../exceptions.js' -import { gasCheck, leading16ZeroBytesCheck, msmGasUsed } from './bls12_381/index.js' -import { moduloLengthCheck } from './util.js' +import { leading16ZeroBytesCheck, msmGasUsed } from './bls12_381/index.js' +import { gasLimitCheck, moduloLengthCheck } from './util.js' import type { EVMBLSInterface, ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' @@ -23,7 +23,7 @@ export async function precompile10(opts: PrecompileInput): Promise { const gasUsedPerPair = opts.common.paramByEIP('Bls12381G2MulGas', 2537) ?? BigInt(0) const gasUsed = msmGasUsed(numPairs, gasUsedPerPair) - if (!gasCheck(opts, gasUsed, 'BLS12G2MSM (0x10)')) { + if (!gasLimitCheck(opts, gasUsed, 'BLS12G2MSM (0x10)')) { return OOGResult(opts.gasLimit) } diff --git a/packages/evm/src/precompiles/11-bls12-pairing.ts b/packages/evm/src/precompiles/11-bls12-pairing.ts index 7d7eb44eed..6c5ad4c107 100644 --- a/packages/evm/src/precompiles/11-bls12-pairing.ts +++ b/packages/evm/src/precompiles/11-bls12-pairing.ts @@ -3,8 +3,8 @@ import { bytesToHex } from '@ethereumjs/util' import { EvmErrorResult, OOGResult } from '../evm.js' import { ERROR, EvmError } from '../exceptions.js' -import { gasCheck, leading16ZeroBytesCheck } from './bls12_381/index.js' -import { moduloLengthCheck } from './util.js' +import { leading16ZeroBytesCheck } from './bls12_381/index.js' +import { gasLimitCheck, moduloLengthCheck } from './util.js' import type { EVMBLSInterface, ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' @@ -32,7 +32,7 @@ export async function precompile11(opts: PrecompileInput): Promise { } const gasUsed = baseGas + gasUsedPerPair * BigInt(Math.floor(opts.data.length / 384)) - if (!gasCheck(opts, gasUsed, 'BLS12PAIRING (0x11)')) { + if (!gasLimitCheck(opts, gasUsed, 'BLS12PAIRING (0x11)')) { return OOGResult(opts.gasLimit) } diff --git a/packages/evm/src/precompiles/12-bls12-map-fp-to-g1.ts b/packages/evm/src/precompiles/12-bls12-map-fp-to-g1.ts index f95792d895..c5f00aa7c3 100644 --- a/packages/evm/src/precompiles/12-bls12-map-fp-to-g1.ts +++ b/packages/evm/src/precompiles/12-bls12-map-fp-to-g1.ts @@ -3,8 +3,8 @@ import { bytesToHex } from '@ethereumjs/util' import { EvmErrorResult, OOGResult } from '../evm.js' import { ERROR, EvmError } from '../exceptions.js' -import { gasCheck, leading16ZeroBytesCheck } from './bls12_381/index.js' -import { equalityLengthCheck } from './util.js' +import { leading16ZeroBytesCheck } from './bls12_381/index.js' +import { equalityLengthCheck, gasLimitCheck } from './util.js' import type { EVMBLSInterface, ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' @@ -14,7 +14,7 @@ export async function precompile12(opts: PrecompileInput): Promise { // note: the gas used is constant; even if the input is incorrect. const gasUsed = opts.common.paramByEIP('Bls12381MapG1Gas', 2537) ?? BigInt(0) - if (!gasCheck(opts, gasUsed, 'BLS12MAPFPTOG1 (0x12)')) { + if (!gasLimitCheck(opts, gasUsed, 'BLS12MAPFPTOG1 (0x12)')) { return OOGResult(opts.gasLimit) } diff --git a/packages/evm/src/precompiles/13-bls12-map-fp2-to-g2.ts b/packages/evm/src/precompiles/13-bls12-map-fp2-to-g2.ts index 2e49d12a18..e9936e70da 100644 --- a/packages/evm/src/precompiles/13-bls12-map-fp2-to-g2.ts +++ b/packages/evm/src/precompiles/13-bls12-map-fp2-to-g2.ts @@ -3,8 +3,8 @@ import { bytesToHex } from '@ethereumjs/util' import { EvmErrorResult, OOGResult } from '../evm.js' import { ERROR, EvmError } from '../exceptions.js' -import { gasCheck, leading16ZeroBytesCheck } from './bls12_381/index.js' -import { equalityLengthCheck } from './util.js' +import { leading16ZeroBytesCheck } from './bls12_381/index.js' +import { equalityLengthCheck, gasLimitCheck } from './util.js' import type { EVMBLSInterface, ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' @@ -14,7 +14,7 @@ export async function precompile13(opts: PrecompileInput): Promise { // note: the gas used is constant; even if the input is incorrect. const gasUsed = opts.common.paramByEIP('Bls12381MapG2Gas', 2537) ?? BigInt(0) - if (!gasCheck(opts, gasUsed, 'BLS12MAPFP2TOG2 (0x13)')) { + if (!gasLimitCheck(opts, gasUsed, 'BLS12MAPFP2TOG2 (0x13)')) { return OOGResult(opts.gasLimit) } diff --git a/packages/evm/src/precompiles/bls12_381/util.ts b/packages/evm/src/precompiles/bls12_381/util.ts index 9d1b6b2a94..d6dbb2f8ef 100644 --- a/packages/evm/src/precompiles/bls12_381/util.ts +++ b/packages/evm/src/precompiles/bls12_381/util.ts @@ -1,4 +1,4 @@ -import { equalsBytes, short } from '@ethereumjs/util' +import { equalsBytes } from '@ethereumjs/util' import { BLS_GAS_DISCOUNT_PAIRS } from './constants.js' @@ -6,31 +6,6 @@ import type { PrecompileInput } from '../types.js' const ZERO_BYTES_16 = new Uint8Array(16) -/** - * Checks that the gas used remain under the gas limit. - * - * @param opts - * @param gasUsed - * @param pName - * @returns - */ -export const gasCheck = (opts: PrecompileInput, gasUsed: bigint, pName: string) => { - if (opts._debug !== undefined) { - opts._debug( - `Run ${pName} precompile data=${short(opts.data)} length=${opts.data.length} gasLimit=${ - opts.gasLimit - } gasUsed=${gasUsed}`, - ) - } - if (opts.gasLimit < gasUsed) { - if (opts._debug !== undefined) { - opts._debug(`${pName} failed: OOG`) - } - return false - } - return true -} - /** * Calculates the gas used for the MSM precompiles based on the number of pairs and * calculating in some discount in relation to the number of pairs. diff --git a/packages/evm/src/precompiles/index.ts b/packages/evm/src/precompiles/index.ts index c2ec81b8dc..5efa2ba93e 100644 --- a/packages/evm/src/precompiles/index.ts +++ b/packages/evm/src/precompiles/index.ts @@ -55,12 +55,12 @@ interface PrecompileAvailabilityCheckTypeEIP { type: PrecompileAvailabilityCheck.EIP param: number } - -const ripemdPrecompileAddress = '0000000000000000000000000000000000000003' +const BYTES_19 = '00000000000000000000000000000000000000' +const ripemdPrecompileAddress = BYTES_19 + '03' const precompileEntries: PrecompileEntry[] = [ { - address: '0000000000000000000000000000000000000001', + address: BYTES_19 + '01', check: { type: PrecompileAvailabilityCheck.Hardfork, param: Hardfork.Chainstart, @@ -69,7 +69,7 @@ const precompileEntries: PrecompileEntry[] = [ name: 'ECRECOVER (0x01)', }, { - address: '0000000000000000000000000000000000000002', + address: BYTES_19 + '02', check: { type: PrecompileAvailabilityCheck.Hardfork, param: Hardfork.Chainstart, @@ -78,7 +78,7 @@ const precompileEntries: PrecompileEntry[] = [ name: 'SHA256 (0x02)', }, { - address: '0000000000000000000000000000000000000003', + address: BYTES_19 + '03', check: { type: PrecompileAvailabilityCheck.Hardfork, param: Hardfork.Chainstart, @@ -87,7 +87,7 @@ const precompileEntries: PrecompileEntry[] = [ name: 'RIPEMD160 (0x03)', }, { - address: '0000000000000000000000000000000000000004', + address: BYTES_19 + '04', check: { type: PrecompileAvailabilityCheck.Hardfork, param: Hardfork.Chainstart, @@ -96,7 +96,7 @@ const precompileEntries: PrecompileEntry[] = [ name: 'Identity (0x04)', }, { - address: '0000000000000000000000000000000000000005', + address: BYTES_19 + '05', check: { type: PrecompileAvailabilityCheck.Hardfork, param: Hardfork.Byzantium, @@ -105,7 +105,7 @@ const precompileEntries: PrecompileEntry[] = [ name: 'MODEXP (0x05)', }, { - address: '0000000000000000000000000000000000000006', + address: BYTES_19 + '06', check: { type: PrecompileAvailabilityCheck.Hardfork, param: Hardfork.Byzantium, @@ -114,7 +114,7 @@ const precompileEntries: PrecompileEntry[] = [ name: 'ECADD (0x06)', }, { - address: '0000000000000000000000000000000000000007', + address: BYTES_19 + '07', check: { type: PrecompileAvailabilityCheck.Hardfork, param: Hardfork.Byzantium, @@ -123,7 +123,7 @@ const precompileEntries: PrecompileEntry[] = [ name: 'ECMUL (0x07)', }, { - address: '0000000000000000000000000000000000000008', + address: BYTES_19 + '08', check: { type: PrecompileAvailabilityCheck.Hardfork, param: Hardfork.Byzantium, @@ -132,7 +132,7 @@ const precompileEntries: PrecompileEntry[] = [ name: 'ECPAIR (0x08)', }, { - address: '0000000000000000000000000000000000000009', + address: BYTES_19 + '09', check: { type: PrecompileAvailabilityCheck.Hardfork, param: Hardfork.Istanbul, @@ -141,7 +141,7 @@ const precompileEntries: PrecompileEntry[] = [ name: 'BLAKE2f (0x09)', }, { - address: '000000000000000000000000000000000000000a', + address: BYTES_19 + '0a', check: { type: PrecompileAvailabilityCheck.EIP, param: 4844, @@ -150,7 +150,7 @@ const precompileEntries: PrecompileEntry[] = [ name: 'KZG (0x0a)', }, { - address: '000000000000000000000000000000000000000b', + address: BYTES_19 + '0b', check: { type: PrecompileAvailabilityCheck.EIP, param: 2537, @@ -159,7 +159,7 @@ const precompileEntries: PrecompileEntry[] = [ name: 'BLS12_G1ADD', }, { - address: '000000000000000000000000000000000000000c', + address: BYTES_19 + '0c', check: { type: PrecompileAvailabilityCheck.EIP, param: 2537, @@ -168,7 +168,7 @@ const precompileEntries: PrecompileEntry[] = [ name: 'BLS12_G1MUL', }, { - address: '000000000000000000000000000000000000000d', + address: BYTES_19 + '0d', check: { type: PrecompileAvailabilityCheck.EIP, param: 2537, @@ -177,7 +177,7 @@ const precompileEntries: PrecompileEntry[] = [ name: 'BLS12_G1MSM', }, { - address: '000000000000000000000000000000000000000e', + address: BYTES_19 + '0e', check: { type: PrecompileAvailabilityCheck.EIP, param: 2537, @@ -186,7 +186,7 @@ const precompileEntries: PrecompileEntry[] = [ name: 'BLS12_G2ADD', }, { - address: '000000000000000000000000000000000000000f', + address: BYTES_19 + '0f', check: { type: PrecompileAvailabilityCheck.EIP, param: 2537, @@ -195,7 +195,7 @@ const precompileEntries: PrecompileEntry[] = [ name: 'BLS12_G2MUL', }, { - address: '0000000000000000000000000000000000000010', + address: BYTES_19 + '10', check: { type: PrecompileAvailabilityCheck.EIP, param: 2537, @@ -204,7 +204,7 @@ const precompileEntries: PrecompileEntry[] = [ name: 'BLS12_G2MSM', }, { - address: '0000000000000000000000000000000000000011', + address: BYTES_19 + '11', check: { type: PrecompileAvailabilityCheck.EIP, param: 2537, @@ -213,7 +213,7 @@ const precompileEntries: PrecompileEntry[] = [ name: 'BLS12_PAIRING', }, { - address: '0000000000000000000000000000000000000012', + address: BYTES_19 + '12', check: { type: PrecompileAvailabilityCheck.EIP, param: 2537, @@ -222,7 +222,7 @@ const precompileEntries: PrecompileEntry[] = [ name: 'BLS12_MAP_FP_TO_G1', }, { - address: '0000000000000000000000000000000000000013', + address: BYTES_19 + '13', check: { type: PrecompileAvailabilityCheck.EIP, param: 2537, @@ -233,25 +233,25 @@ const precompileEntries: PrecompileEntry[] = [ ] const precompiles: Precompiles = { - '0000000000000000000000000000000000000001': precompile01, - '0000000000000000000000000000000000000002': precompile02, + [BYTES_19 + '01']: precompile01, + [BYTES_19 + '02']: precompile02, [ripemdPrecompileAddress]: precompile03, - '0000000000000000000000000000000000000004': precompile04, - '0000000000000000000000000000000000000005': precompile05, - '0000000000000000000000000000000000000006': precompile06, - '0000000000000000000000000000000000000007': precompile07, - '0000000000000000000000000000000000000008': precompile08, - '0000000000000000000000000000000000000009': precompile09, - '000000000000000000000000000000000000000a': precompile0a, - '000000000000000000000000000000000000000b': precompile0b, - '000000000000000000000000000000000000000c': precompile0c, - '000000000000000000000000000000000000000d': precompile0d, - '000000000000000000000000000000000000000e': precompile0e, - '000000000000000000000000000000000000000f': precompile0f, - '0000000000000000000000000000000000000010': precompile10, - '0000000000000000000000000000000000000011': precompile11, - '0000000000000000000000000000000000000012': precompile12, - '0000000000000000000000000000000000000013': precompile13, + [BYTES_19 + '04']: precompile04, + [BYTES_19 + '05']: precompile05, + [BYTES_19 + '06']: precompile06, + [BYTES_19 + '07']: precompile07, + [BYTES_19 + '08']: precompile08, + [BYTES_19 + '09']: precompile09, + [BYTES_19 + '0a']: precompile0a, + [BYTES_19 + '0b']: precompile0b, + [BYTES_19 + '0c']: precompile0c, + [BYTES_19 + '0d']: precompile0d, + [BYTES_19 + '0e']: precompile0e, + [BYTES_19 + '0f']: precompile0f, + [BYTES_19 + '10']: precompile10, + [BYTES_19 + '11']: precompile11, + [BYTES_19 + '12']: precompile12, + [BYTES_19 + '13']: precompile13, } type DeletePrecompile = { diff --git a/packages/evm/src/precompiles/util.ts b/packages/evm/src/precompiles/util.ts index e8dc82d306..24afd52fea 100644 --- a/packages/evm/src/precompiles/util.ts +++ b/packages/evm/src/precompiles/util.ts @@ -1,5 +1,33 @@ +import { short } from '@ethereumjs/util' + import type { PrecompileInput } from './index.js' +/** + * Checks that the gas used remain under the gas limit. + * + * @param opts + * @param gasUsed + * @param pName + * @returns + */ +export const gasLimitCheck = (opts: PrecompileInput, gasUsed: bigint, pName: string) => { + if (opts._debug !== undefined) { + opts._debug( + `Run ${pName} precompile data=${short(opts.data)} length=${ + opts.data.length + } gasLimit=${opts.gasLimit} gasUsed=${gasUsed}`, + ) + } + + if (opts.gasLimit < gasUsed) { + if (opts._debug !== undefined) { + opts._debug(`${pName} failed: OOG`) + } + return false + } + return true +} + /** * Checks that the length of the provided data is equal to `length`. * diff --git a/packages/evm/src/types.ts b/packages/evm/src/types.ts index b359d1c69f..eb4a690002 100644 --- a/packages/evm/src/types.ts +++ b/packages/evm/src/types.ts @@ -343,9 +343,13 @@ export interface EVMOpts { stateManager?: StateManagerInterface /** + * The EVM comes with a basic mock blockchain interface and implementation for + * non-block containing use cases. * + * For block-containing setups use the full blockchain implementation from the + * `@ethereumjs/blockchain package. */ - blockchain?: Blockchain + blockchain?: EVMMockBlockchainInterface /** * @@ -472,16 +476,17 @@ export interface TransientStorageInterface { clear(): void } -type MockBlock = { +export type EVMMockBlock = { hash(): Uint8Array } -export interface Blockchain { - getBlock(blockId: number): Promise - shallowCopy(): Blockchain +export interface EVMMockBlockchainInterface { + getBlock(blockId: number): Promise + putBlock(block: EVMMockBlock): Promise + shallowCopy(): EVMMockBlockchainInterface } -export class DefaultBlockchain implements Blockchain { +export class EVMMockBlockchain implements EVMMockBlockchainInterface { async getBlock() { return { hash() { @@ -489,6 +494,7 @@ export class DefaultBlockchain implements Blockchain { }, } } + async putBlock() {} shallowCopy() { return this } diff --git a/packages/statemanager/src/stateManager.ts b/packages/statemanager/src/stateManager.ts index 0fa21094f3..1bd2ae6869 100644 --- a/packages/statemanager/src/stateManager.ts +++ b/packages/statemanager/src/stateManager.ts @@ -202,9 +202,15 @@ export class DefaultStateManager implements StateManagerInterface { * @param value - The value of the `code` */ async putCode(address: Address, value: Uint8Array): Promise { - this._caches?.code?.put(address, value) const codeHash = this.keccakFunction(value) + if (this._caches?.code !== undefined) { + this._caches!.code!.put(address, value) + } else { + const key = this._prefixCodeHashes ? concatBytes(CODEHASH_PREFIX, codeHash) : codeHash + await this._getCodeDB().put(key, value) + } + if (this.DEBUG) { this._debug(`Update codeHash (-> ${short(codeHash)}) for account ${address}`) } diff --git a/packages/statemanager/src/statelessVerkleStateManager.ts b/packages/statemanager/src/statelessVerkleStateManager.ts index c6603ae295..8febedcfe8 100644 --- a/packages/statemanager/src/statelessVerkleStateManager.ts +++ b/packages/statemanager/src/statelessVerkleStateManager.ts @@ -25,10 +25,11 @@ import debugDefault from 'debug' import { keccak256 } from 'ethereum-cryptography/keccak.js' import { AccessWitness, AccessedStateType, decodeValue } from './accessWitness.js' -import { Caches, OriginalStorageCache } from './cache/index.js' +import { OriginalStorageCache } from './cache/index.js' import { modifyAccountFields } from './util.js' import type { AccessedStateWithAddress } from './accessWitness.js' +import type { Caches } from './cache/index.js' import type { StatelessVerkleStateManagerOpts, VerkleState } from './index.js' import type { DefaultStateManager } from './stateManager.js' import type { AccountFields, Proof, StateManagerInterface } from '@ethereumjs/common' @@ -206,9 +207,9 @@ export class StatelessVerkleStateManager implements StateManagerInterface { * at the last fully committed point, i.e. as if all current * checkpoints were reverted. */ - shallowCopy(): StatelessVerkleStateManager { + shallowCopy(downlevelCaches = true): StatelessVerkleStateManager { const stateManager = new StatelessVerkleStateManager({ - caches: this._caches !== undefined ? new Caches() : undefined, + caches: this._caches?.shallowCopy(downlevelCaches), verkleCrypto: this.verkleCrypto, }) return stateManager @@ -519,7 +520,7 @@ export class StatelessVerkleStateManager implements StateManagerInterface { * @param {Uint8Array} stateRoot - The stateRoot to verify the executionWitness against * @returns {boolean} - Returns true if the executionWitness matches the provided stateRoot, otherwise false */ - verifyProof(stateRoot: Uint8Array): boolean { + verifyVerkleProof(stateRoot: Uint8Array): boolean { if (this._executionWitness === undefined) { debug('Missing executionWitness') return false diff --git a/packages/statemanager/test/stateManager.code.spec.ts b/packages/statemanager/test/stateManager.code.spec.ts index a31026d662..02c563c92b 100644 --- a/packages/statemanager/test/stateManager.code.spec.ts +++ b/packages/statemanager/test/stateManager.code.spec.ts @@ -15,162 +15,167 @@ import type { AccountData } from '@ethereumjs/util' describe('StateManager -> Code', () => { for (const accountCacheOpts of [{ size: 1000 }, { size: 0 }]) { - it(`should store codehashes using a prefix`, async () => { - /* - This test is mostly an example of why a code prefix is necessary - I an address, we put two storage values. The preimage of the (storage trie) root hash is known - This preimage is used as codeHash - - NOTE: Currently, the only problem which this code prefix fixes, is putting 0x80 as contract code - -> This hashes to the empty trie node hash (0x80 = RLP([])), so keccak256(0x80) = empty trie node hash - -> Therefore, each empty state trie now points to 0x80, which is not a valid trie node, which crashes @ethereumjs/trie - */ - - // Setup - const stateManager = new DefaultStateManager({ - caches: new Caches({ account: accountCacheOpts }), + for (const codeCacheOpts of [{ size: 1000 }, { size: 0 }]) { + it(`should store codehashes using a prefix`, async () => { + /* + This test is mostly an example of why a code prefix is necessary + I an address, we put two storage values. The preimage of the (storage trie) root hash is known + This preimage is used as codeHash + + NOTE: Currently, the only problem which this code prefix fixes, is putting 0x80 as contract code + -> This hashes to the empty trie node hash (0x80 = RLP([])), so keccak256(0x80) = empty trie node hash + -> Therefore, each empty state trie now points to 0x80, which is not a valid trie node, which crashes @ethereumjs/trie + */ + + // Setup + const stateManager = new DefaultStateManager({ + caches: new Caches({ account: accountCacheOpts, code: codeCacheOpts }), + }) + const codeStateManager = new DefaultStateManager({ + caches: new Caches({ account: accountCacheOpts, code: codeCacheOpts }), + }) + const address1 = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')) + const account = createAccountWithDefaults() + const key1 = hexToBytes(`0x${'00'.repeat(32)}`) + const key2 = hexToBytes(`0x${'00'.repeat(31)}01`) + + await stateManager.putAccount(address1, account) + await stateManager.putStorage(address1, key1, key2) + await stateManager.putStorage(address1, key2, key2) + const root = await stateManager.getStateRoot() + const rawNode = await stateManager['_trie']['_db'].get(root) + + await codeStateManager.putCode(address1, rawNode!) + + let codeSlot1 = await codeStateManager.getStorage(address1, key1) + let codeSlot2 = await codeStateManager.getStorage(address1, key2) + + assert.ok(codeSlot1.length === 0, 'slot 0 is empty') + assert.ok(codeSlot2.length === 0, 'slot 1 is empty') + + const code = await codeStateManager.getCode(address1) + assert.ok(code.length > 0, 'code deposited correctly') + + const slot1 = await stateManager.getStorage(address1, key1) + const slot2 = await stateManager.getStorage(address1, key2) + + assert.ok(slot1.length > 0, 'storage key0 deposited correctly') + assert.ok(slot2.length > 0, 'storage key1 deposited correctly') + + let slotCode = await stateManager.getCode(address1) + assert.ok(slotCode.length === 0, 'code cannot be loaded') + + // Checks by either setting state root to codeHash, or codeHash to stateRoot + // The knowledge of the tries should not change + let account1 = await stateManager.getAccount(address1) + account1!.codeHash = root + + await stateManager.putAccount(address1, account1!) + + slotCode = await stateManager.getCode(address1) + assert.ok(slotCode.length === 0, 'code cannot be loaded') // This test fails if no code prefix is used + + account1 = await codeStateManager.getAccount(address1) + account1!.storageRoot = root + + await codeStateManager.putAccount(address1, account1!) + + codeSlot1 = await codeStateManager.getStorage(address1, key1) + codeSlot2 = await codeStateManager.getStorage(address1, key2) + + assert.ok(codeSlot1.length === 0, 'slot 0 is empty') + assert.ok(codeSlot2.length === 0, 'slot 1 is empty') }) - const codeStateManager = new DefaultStateManager({ - caches: new Caches({ account: accountCacheOpts }), - }) - const address1 = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')) - const account = createAccountWithDefaults() - const key1 = hexToBytes(`0x${'00'.repeat(32)}`) - const key2 = hexToBytes(`0x${'00'.repeat(31)}01`) - - await stateManager.putAccount(address1, account) - await stateManager.putStorage(address1, key1, key2) - await stateManager.putStorage(address1, key2, key2) - const root = await stateManager.getStateRoot() - const rawNode = await stateManager['_trie']['_db'].get(root) - - await codeStateManager.putCode(address1, rawNode!) - - let codeSlot1 = await codeStateManager.getStorage(address1, key1) - let codeSlot2 = await codeStateManager.getStorage(address1, key2) - - assert.ok(codeSlot1.length === 0, 'slot 0 is empty') - assert.ok(codeSlot2.length === 0, 'slot 1 is empty') - - const code = await codeStateManager.getCode(address1) - assert.ok(code.length > 0, 'code deposited correctly') - - const slot1 = await stateManager.getStorage(address1, key1) - const slot2 = await stateManager.getStorage(address1, key2) - - assert.ok(slot1.length > 0, 'storage key0 deposited correctly') - assert.ok(slot2.length > 0, 'storage key1 deposited correctly') - - let slotCode = await stateManager.getCode(address1) - assert.ok(slotCode.length === 0, 'code cannot be loaded') - - // Checks by either setting state root to codeHash, or codeHash to stateRoot - // The knowledge of the tries should not change - let account1 = await stateManager.getAccount(address1) - account1!.codeHash = root - - await stateManager.putAccount(address1, account1!) - - slotCode = await stateManager.getCode(address1) - assert.ok(slotCode.length === 0, 'code cannot be loaded') // This test fails if no code prefix is used - account1 = await codeStateManager.getAccount(address1) - account1!.storageRoot = root - - await codeStateManager.putAccount(address1, account1!) - - codeSlot1 = await codeStateManager.getStorage(address1, key1) - codeSlot2 = await codeStateManager.getStorage(address1, key2) - - assert.ok(codeSlot1.length === 0, 'slot 0 is empty') - assert.ok(codeSlot2.length === 0, 'slot 1 is empty') - }) + it(`should set and get code`, async () => { + const stateManager = new DefaultStateManager({ + caches: new Caches({ account: accountCacheOpts, code: codeCacheOpts }), + }) + const address = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')) + const code = hexToBytes( + '0x73095e7baea6a6c7c4c2dfeb977efac326af552d873173095e7baea6a6c7c4c2dfeb977efac326af552d873157', + ) + const raw: AccountData = { + nonce: '0x0', + balance: '0x03e7', + codeHash: '0xb30fb32201fe0486606ad451e1a61e2ae1748343cd3d411ed992ffcc0774edd4', + } + const account = createAccount(raw) + await stateManager.putAccount(address, account) + await stateManager.putCode(address, code) + const codeRetrieved = await stateManager.getCode(address) + assert.ok(equalsBytes(code, codeRetrieved)) + }) - it(`should set and get code`, async () => { - const stateManager = new DefaultStateManager({ - caches: new Caches({ account: accountCacheOpts }), + it(`should not get code if is not contract`, async () => { + const stateManager = new DefaultStateManager({ + caches: new Caches({ account: accountCacheOpts, code: codeCacheOpts }), + }) + const address = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')) + const raw: AccountData = { + nonce: '0x0', + balance: '0x03e7', + } + const account = createAccount(raw) + await stateManager.putAccount(address, account) + const code = await stateManager.getCode(address) + assert.ok(equalsBytes(code, new Uint8Array(0))) }) - const address = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')) - const code = hexToBytes( - '0x73095e7baea6a6c7c4c2dfeb977efac326af552d873173095e7baea6a6c7c4c2dfeb977efac326af552d873157', - ) - const raw: AccountData = { - nonce: '0x0', - balance: '0x03e7', - codeHash: '0xb30fb32201fe0486606ad451e1a61e2ae1748343cd3d411ed992ffcc0774edd4', - } - const account = createAccount(raw) - await stateManager.putAccount(address, account) - await stateManager.putCode(address, code) - const codeRetrieved = await stateManager.getCode(address) - assert.ok(equalsBytes(code, codeRetrieved)) - }) - - it(`should not get code if is not contract`, async () => { - const stateManager = new DefaultStateManager({ - caches: new Caches({ account: accountCacheOpts }), + + it(`should set empty code`, async () => { + const stateManager = new DefaultStateManager({ + caches: new Caches({ account: accountCacheOpts, code: codeCacheOpts }), + }) + const address = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')) + const raw: AccountData = { + nonce: '0x0', + balance: '0x03e7', + } + const account = createAccount(raw) + const code = new Uint8Array(0) + await stateManager.putAccount(address, account) + await stateManager.putCode(address, code) + const codeRetrieved = await stateManager.getCode(address) + assert.ok(equalsBytes(codeRetrieved, new Uint8Array(0))) }) - const address = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')) - const raw: AccountData = { - nonce: '0x0', - balance: '0x03e7', - } - const account = createAccount(raw) - await stateManager.putAccount(address, account) - const code = await stateManager.getCode(address) - assert.ok(equalsBytes(code, new Uint8Array(0))) - }) - - it(`should set empty code`, async () => { - const stateManager = new DefaultStateManager({ - caches: new Caches({ account: accountCacheOpts }), + + it(`should prefix codehashes by default`, async () => { + const stateManager = new DefaultStateManager({ + caches: new Caches({ account: accountCacheOpts, code: codeCacheOpts }), + }) + const address = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')) + const code = hexToBytes('0x80') + await stateManager.putCode(address, code) + const codeRetrieved = await stateManager.getCode(address) + assert.ok(equalsBytes(codeRetrieved, code)) }) - const address = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')) - const raw: AccountData = { - nonce: '0x0', - balance: '0x03e7', - } - const account = createAccount(raw) - const code = new Uint8Array(0) - await stateManager.putAccount(address, account) - await stateManager.putCode(address, code) - const codeRetrieved = await stateManager.getCode(address) - assert.ok(equalsBytes(codeRetrieved, new Uint8Array(0))) - }) - - it(`should prefix codehashes by default`, async () => { - const stateManager = new DefaultStateManager({ - caches: new Caches({ account: accountCacheOpts }), + + it(`should not prefix codehashes if prefixCodeHashes = false`, async () => { + const stateManager = new DefaultStateManager({ + prefixCodeHashes: false, + caches: new Caches({ account: accountCacheOpts, code: codeCacheOpts }), + }) + const address = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')) + const code = hexToBytes('0x80') + try { + await stateManager.putCode(address, code) + assert.fail('should throw') + } catch (e) { + assert.ok(true, 'successfully threw') + } }) - const address = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')) - const code = hexToBytes('0x80') - await stateManager.putCode(address, code) - const codeRetrieved = await stateManager.getCode(address) - assert.ok(equalsBytes(codeRetrieved, code)) - }) - - it(`should not prefix codehashes if prefixCodeHashes = false`, async () => { - const stateManager = new DefaultStateManager({ - prefixCodeHashes: false, + + it('putCode with empty code on existing address should correctly propagate', async () => { + const stateManager = new DefaultStateManager({ + caches: new Caches({ account: accountCacheOpts, code: codeCacheOpts }), + }) + const address = createZeroAddress() + await stateManager.putCode(address, new Uint8Array([1])) + await stateManager.putCode(address, new Uint8Array()) + const account = await stateManager.getAccount(address) + assert.ok(account !== undefined) + assert.ok(account?.isEmpty()) }) - const address = new Address(hexToBytes('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')) - const code = hexToBytes('0x80') - try { - await stateManager.putCode(address, code) - assert.fail('should throw') - } catch (e) { - assert.ok(true, 'successfully threw') - } - }) - - it('putCode with empty code on existing address should correctly propagate', async () => { - const stateManager = new DefaultStateManager() - const address = createZeroAddress() - await stateManager.putCode(address, new Uint8Array([1])) - await stateManager.putCode(address, new Uint8Array()) - const account = await stateManager.getAccount(address) - assert.ok(account !== undefined) - assert.ok(account?.isEmpty()) - }) + } } }) diff --git a/packages/vm/package.json b/packages/vm/package.json index 0bef3ed56c..82cac1c74b 100644 --- a/packages/vm/package.json +++ b/packages/vm/package.json @@ -65,7 +65,6 @@ }, "dependencies": { "@ethereumjs/block": "^5.3.0", - "@ethereumjs/blockchain": "^7.3.0", "@ethereumjs/common": "^4.4.0", "@ethereumjs/evm": "^3.1.0", "@ethereumjs/rlp": "^5.0.2", @@ -77,6 +76,7 @@ "ethereum-cryptography": "^2.2.1" }, "devDependencies": { + "@ethereumjs/blockchain": "^7.3.0", "@ethereumjs/ethash": "^3.0.3", "@ethersproject/abi": "^5.0.12", "@types/benchmark": "^1.0.33", diff --git a/packages/vm/src/runBlock.ts b/packages/vm/src/runBlock.ts index f7e7b20aea..409b844728 100644 --- a/packages/vm/src/runBlock.ts +++ b/packages/vm/src/runBlock.ts @@ -1,7 +1,6 @@ import { createBlock, genRequestsTrieRoot } from '@ethereumjs/block' import { ConsensusType, Hardfork } from '@ethereumjs/common' import { RLP } from '@ethereumjs/rlp' -import { StatelessVerkleStateManager } from '@ethereumjs/statemanager' import { Trie } from '@ethereumjs/trie' import { TransactionType } from '@ethereumjs/tx' import { @@ -134,7 +133,7 @@ export async function runBlock(vm: VM, opts: RunBlockOpts): Promise { console.time(entireTxLabel) } - // create a reasonable default if no block is given - opts.block = opts.block ?? createBlock({}, { common: vm.common }) - - if (opts.skipHardForkValidation !== true) { + if (opts.skipHardForkValidation !== true && opts.block !== undefined) { // If block and tx don't have a same hardfork, set tx hardfork to block if (opts.tx.common.hardfork() !== opts.block.common.hardfork()) { opts.tx.common.setHardfork(opts.block.common.hardfork()) @@ -97,7 +95,8 @@ export async function runTx(vm: VM, opts: RunTxOpts): Promise { } } - if (opts.skipBlockGasLimitValidation !== true && opts.block.header.gasLimit < opts.tx.gasLimit) { + const gasLimit = opts.block?.header.gasLimit ?? DEFAULT_HEADER.gasLimit + if (opts.skipBlockGasLimitValidation !== true && gasLimit < opts.tx.gasLimit) { const msg = _errorMsg('tx has a higher gas limit than the block', vm, opts.block, opts.tx) throw new Error(msg) } @@ -191,19 +190,15 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { let stateAccesses if (vm.common.isActivatedEIP(6800)) { - if (!(vm.stateManager instanceof StatelessVerkleStateManager)) { + if (typeof vm.stateManager.initVerkleExecutionWitness !== 'function') { throw Error(`StatelessVerkleStateManager needed for execution of verkle blocks`) } - stateAccesses = (vm.stateManager as StatelessVerkleStateManager).accessWitness + stateAccesses = vm.stateManager.accessWitness! } const txAccesses = stateAccesses?.shallowCopy() const { tx, block } = opts - if (!block) { - throw new Error('block required') - } - /** * The `beforeTx` event * @@ -234,7 +229,8 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { vm.evm.journal.addAlwaysWarmAddress(bytesToUnprefixedHex(tx.to.bytes)) } if (vm.common.isActivatedEIP(3651)) { - vm.evm.journal.addAlwaysWarmAddress(bytesToUnprefixedHex(block.header.coinbase.bytes)) + const coinbase = block?.header.coinbase.bytes ?? DEFAULT_HEADER.coinbase.bytes + vm.evm.journal.addAlwaysWarmAddress(bytesToUnprefixedHex(coinbase)) } } @@ -262,7 +258,7 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { // Ensure that the user was willing to at least pay the base fee // assert transaction.max_fee_per_gas >= block.base_fee_per_gas const maxFeePerGas = 'maxFeePerGas' in tx ? tx.maxFeePerGas : tx.gasPrice - const baseFeePerGas = block.header.baseFeePerGas! + const baseFeePerGas = block?.header.baseFeePerGas ?? DEFAULT_HEADER.baseFeePerGas! if (maxFeePerGas < baseFeePerGas) { const msg = _errorMsg( `Transaction's ${ @@ -311,7 +307,8 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { } // Check balance against upfront tx cost - const upFrontCost = tx.getUpfrontCost(block.header.baseFeePerGas) + const baseFeePerGas = block?.header.baseFeePerGas ?? DEFAULT_HEADER.baseFeePerGas + const upFrontCost = tx.getUpfrontCost(baseFeePerGas) if (balance < upFrontCost) { if (opts.skipBalance === true && fromAccount.balance < upFrontCost) { if (tx.supports(Capability.EIP1559FeeMarket) === false) { @@ -341,7 +338,7 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { maxCost += tx.gasLimit * (tx as FeeMarket1559Tx).maxFeePerGas } - if (tx instanceof Blob4844Tx) { + if (isBlob4844Tx(tx)) { if (!vm.common.isActivatedEIP(4844)) { const msg = _errorMsg('blob transactions are only valid with EIP4844 active', vm, block, tx) throw new Error(msg) @@ -349,24 +346,14 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { // EIP-4844 spec // the signer must be able to afford the transaction // assert signer(tx).balance >= tx.message.gas * tx.message.max_fee_per_gas + get_total_data_gas(tx) * tx.message.max_fee_per_data_gas - const castTx = tx as Blob4844Tx - totalblobGas = vm.common.param('blobGasPerBlob') * BigInt(castTx.numBlobs()) - maxCost += totalblobGas * castTx.maxFeePerBlobGas + totalblobGas = vm.common.param('blobGasPerBlob') * BigInt(tx.numBlobs()) + maxCost += totalblobGas * tx.maxFeePerBlobGas // 4844 minimum blobGas price check - if (opts.block === undefined) { + blobGasPrice = opts.block?.header.getBlobGasPrice() ?? DEFAULT_HEADER.getBlobGasPrice() + if (tx.maxFeePerBlobGas < blobGasPrice) { const msg = _errorMsg( - `Block option must be supplied to compute blob gas price`, - vm, - block, - tx, - ) - throw new Error(msg) - } - blobGasPrice = opts.block.header.getBlobGasPrice() - if (castTx.maxFeePerBlobGas < blobGasPrice) { - const msg = _errorMsg( - `Transaction's maxFeePerBlobGas ${castTx.maxFeePerBlobGas}) is less than block blobGasPrice (${blobGasPrice}).`, + `Transaction's maxFeePerBlobGas ${tx.maxFeePerBlobGas}) is less than block blobGasPrice (${blobGasPrice}).`, vm, block, tx, @@ -408,7 +395,7 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { // EIP-1559 tx if (tx.supports(Capability.EIP1559FeeMarket)) { // TODO make txs use the new getEffectivePriorityFee - const baseFee = block.header.baseFeePerGas! + const baseFee = block?.header.baseFeePerGas ?? DEFAULT_HEADER.baseFeePerGas! inclusionFeePerGas = tx.getEffectivePriorityFee(baseFee) gasPrice = inclusionFeePerGas + baseFee @@ -416,15 +403,15 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { // Have to cast as legacy tx since EIP1559 tx does not have gas price gasPrice = (tx).gasPrice if (vm.common.isActivatedEIP(1559)) { - const baseFee = block.header.baseFeePerGas! + const baseFee = block?.header.baseFeePerGas ?? DEFAULT_HEADER.baseFeePerGas! inclusionFeePerGas = (tx).gasPrice - baseFee } } // EIP-4844 tx let blobVersionedHashes - if (tx instanceof Blob4844Tx) { - blobVersionedHashes = (tx as Blob4844Tx).blobVersionedHashes + if (isBlob4844Tx(tx)) { + blobVersionedHashes = tx.blobVersionedHashes } // Update from account's balance @@ -627,15 +614,15 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { // Update miner's balance let miner if (vm.common.consensusType() === ConsensusType.ProofOfAuthority) { - miner = cliqueSigner(block.header) + miner = cliqueSigner(block?.header ?? DEFAULT_HEADER) } else { - miner = block.header.coinbase + miner = block?.header.coinbase ?? DEFAULT_HEADER.coinbase } let minerAccount = await state.getAccount(miner) if (minerAccount === undefined) { if (vm.common.isActivatedEIP(6800)) { - ;(state as StatelessVerkleStateManager).accessWitness!.touchAndChargeProofOfAbsence(miner) + state.accessWitness!.touchAndChargeProofOfAbsence(miner) } minerAccount = new Account() } @@ -647,7 +634,7 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { if (vm.common.isActivatedEIP(6800)) { // use vm utility to build access but the computed gas is not charged and hence free - ;(state as StatelessVerkleStateManager).accessWitness!.touchTxTargetAndComputeGas(miner, { + state.accessWitness!.touchTxTargetAndComputeGas(miner, { sendsValue: true, }) } @@ -741,7 +728,10 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { } // Generate the tx receipt - const gasUsed = opts.blockGasUsed !== undefined ? opts.blockGasUsed : block.header.gasUsed + const gasUsed = + opts.blockGasUsed !== undefined + ? opts.blockGasUsed + : (block?.header.gasUsed ?? DEFAULT_HEADER.gasUsed) const cumulativeGasUsed = gasUsed + results.totalGasSpent results.receipt = await generateTxReceipt( vm, @@ -873,8 +863,9 @@ export async function generateTxReceipt( * @param msg Base error message * @hidden */ -function _errorMsg(msg: string, vm: VM, block: Block, tx: TypedTransaction) { - const blockErrorStr = 'errorStr' in block ? block.errorStr() : 'block' +function _errorMsg(msg: string, vm: VM, block: Block | undefined, tx: TypedTransaction) { + const blockOrHeader = block ?? DEFAULT_HEADER + const blockErrorStr = 'errorStr' in blockOrHeader ? blockOrHeader.errorStr() : 'block' const txErrorStr = 'errorStr' in tx ? tx.errorStr() : 'tx' const errorMsg = `${msg} (${vm.errorStr()} -> ${blockErrorStr} -> ${txErrorStr})` diff --git a/packages/vm/src/types.ts b/packages/vm/src/types.ts index 3171143edf..0e3d7e49cd 100644 --- a/packages/vm/src/types.ts +++ b/packages/vm/src/types.ts @@ -1,8 +1,13 @@ import type { Bloom } from './bloom/index.js' import type { Block, BlockOptions, HeaderData } from '@ethereumjs/block' -import type { BlockchainInterface } from '@ethereumjs/blockchain' import type { Common, ParamsDict, StateManagerInterface } from '@ethereumjs/common' -import type { EVMInterface, EVMOpts, EVMResult, Log } from '@ethereumjs/evm' +import type { + EVMInterface, + EVMMockBlockchainInterface, + EVMOpts, + EVMResult, + Log, +} from '@ethereumjs/evm' import type { AccessList, TypedTransaction } from '@ethereumjs/tx' import type { BigIntLike, @@ -121,7 +126,7 @@ export interface VMOpts { /** * A {@link Blockchain} object for storing/retrieving blocks */ - blockchain?: BlockchainInterface + blockchain?: EVMMockBlockchainInterface /** * If true, create entries in the state tree for the precompiled contracts, saving some gas the * first time each of them is called. diff --git a/packages/vm/src/vm.ts b/packages/vm/src/vm.ts index 0b69e3dfe0..4280c4243a 100644 --- a/packages/vm/src/vm.ts +++ b/packages/vm/src/vm.ts @@ -1,7 +1,6 @@ -import { createBlockchain } from '@ethereumjs/blockchain' import { Common, Mainnet } from '@ethereumjs/common' -import { createEVM, getActivePrecompiles } from '@ethereumjs/evm' -import { Caches, DefaultStateManager } from '@ethereumjs/statemanager' +import { EVMMockBlockchain, createEVM, getActivePrecompiles } from '@ethereumjs/evm' +import { DefaultStateManager } from '@ethereumjs/statemanager' import { Account, Address, @@ -13,9 +12,8 @@ import { import { paramsVM } from './params.js' import type { VMEvents, VMOpts } from './types.js' -import type { BlockchainInterface } from '@ethereumjs/blockchain' import type { StateManagerInterface } from '@ethereumjs/common' -import type { EVMInterface } from '@ethereumjs/evm' +import type { EVMInterface, EVMMockBlockchainInterface } from '@ethereumjs/evm' import type { BigIntLike } from '@ethereumjs/util' /** @@ -33,7 +31,7 @@ export class VM { /** * The blockchain the VM operates on */ - readonly blockchain: BlockchainInterface + readonly blockchain: EVMMockBlockchainInterface readonly common: Common @@ -83,13 +81,12 @@ export class VM { if (opts.stateManager === undefined) { opts.stateManager = new DefaultStateManager({ - caches: new Caches(), common: opts.common, }) } if (opts.blockchain === undefined) { - opts.blockchain = await createBlockchain({ common: opts.common }) + opts.blockchain = new EVMMockBlockchain() } if (opts.profilerOpts !== undefined) { diff --git a/packages/vm/test/api/EIPs/eip-4399-supplant-difficulty-opcode-with-prevrando.spec.ts b/packages/vm/test/api/EIPs/eip-4399-supplant-difficulty-opcode-with-prevrando.spec.ts index 26d3472296..1b83ccd680 100644 --- a/packages/vm/test/api/EIPs/eip-4399-supplant-difficulty-opcode-with-prevrando.spec.ts +++ b/packages/vm/test/api/EIPs/eip-4399-supplant-difficulty-opcode-with-prevrando.spec.ts @@ -1,4 +1,5 @@ import { createBlock } from '@ethereumjs/block' +import { createBlockchain } from '@ethereumjs/blockchain' import { Common, Hardfork, Mainnet } from '@ethereumjs/common' import { bytesToBigInt, hexToBytes } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' @@ -10,9 +11,10 @@ import type { InterpreterStep } from '@ethereumjs/evm' describe('EIP-4399 -> 0x44 (DIFFICULTY) should return PREVRANDAO', () => { it('should return the right values', async () => { const common = new Common({ chain: Mainnet, hardfork: Hardfork.London }) - const vm = await VM.create({ common }) + const blockchain = await createBlockchain() + const vm = await VM.create({ common, blockchain }) - const genesis = await vm.blockchain.getCanonicalHeadBlock!() + const genesis = await blockchain.getCanonicalHeadBlock!() const header = { number: 1, parentHash: genesis.header.hash(), diff --git a/packages/vm/test/api/EIPs/eip-4895-withdrawals.spec.ts b/packages/vm/test/api/EIPs/eip-4895-withdrawals.spec.ts index 55b4764425..e37582d931 100644 --- a/packages/vm/test/api/EIPs/eip-4895-withdrawals.spec.ts +++ b/packages/vm/test/api/EIPs/eip-4895-withdrawals.spec.ts @@ -34,7 +34,8 @@ const gethWithdrawals8BlockRlp = describe('EIP4895 tests', () => { it('EIP4895: withdrawals execute as expected', async () => { - const vm = await VM.create({ common }) + const blockchain = await createBlockchain() + const vm = await VM.create({ common, blockchain }) const withdrawals = [] const addresses = ['20'.repeat(20), '30'.repeat(20), '40'.repeat(20)] const amounts = [BigInt(1000), BigInt(3000), BigInt(5000)] @@ -128,7 +129,8 @@ describe('EIP4895 tests', () => { }) it('EIP4895: state update should exclude 0 amount updates', async () => { - const vm = await VM.create({ common }) + const blockchain = await createBlockchain() + const vm = await VM.create({ common, blockchain }) await vm.stateManager.generateCanonicalGenesis!(parseGethGenesisState(genesisJSON)) const preState = bytesToHex(await vm.stateManager.getStateRoot()) diff --git a/packages/vm/test/api/copy.spec.ts b/packages/vm/test/api/copy.spec.ts index fb785481f7..1c4f21895d 100644 --- a/packages/vm/test/api/copy.spec.ts +++ b/packages/vm/test/api/copy.spec.ts @@ -18,12 +18,6 @@ describe('VM Copy Test', () => { 'account exists before copy', ) - const vmCopy = await vm.shallowCopy() - assert.isUndefined( - await vmCopy.stateManager.getAccount(address), - 'non-committed checkpoints will not be copied', - ) - await vm.stateManager.checkpoint() await vm.stateManager.commit() diff --git a/packages/vm/test/api/runBlock.spec.ts b/packages/vm/test/api/runBlock.spec.ts index 2e009abac0..2ec22c5ef8 100644 --- a/packages/vm/test/api/runBlock.spec.ts +++ b/packages/vm/test/api/runBlock.spec.ts @@ -4,6 +4,7 @@ import { createBlockFromRLPSerializedBlock, createSealedCliqueBlock, } from '@ethereumjs/block' +import { createBlockchain } from '@ethereumjs/blockchain' import { Common, Goerli, Hardfork, Mainnet, createCustomCommon } from '@ethereumjs/common' import { RLP } from '@ethereumjs/rlp' import { @@ -253,7 +254,8 @@ describe('runBlock() -> API parameter usage/data errors', async () => { }) it('should fail when block validation fails', async () => { - const vm = await VM.create({ common }) + const blockchain = await createBlockchain() + const vm = await VM.create({ common, blockchain }) const blockRlp = hexToBytes(testData.default.blocks[0].rlp as PrefixedHexString) const block = Object.create(createBlockFromRLPSerializedBlock(blockRlp, { common }))