Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions packages/evm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
| TypeScript implementation of the Ethereum EVM. |
| ---------------------------------------------- |

- 🦄 All hardforks up till **Pectra**
- 🦄 All hardforks up to **Pectra**
- 🌴 Tree-shakeable API
- 👷🏼 Controlled dependency set (7 external + `@Noble` crypto)
- 🧩 Flexible EIP on/off engine
- 🛠️ Custom precompiles
- 🚀 Build-in profiler
- 🚀 Built-in profiler
- 🪢 User-friendly colored debugging
- 🛵 422KB bundle size (110KB gzipped)
- 🏄🏾‍♂️ WASM-free default + Fully browser ready
Expand Down Expand Up @@ -73,7 +73,7 @@ void main()

### Blockchain, State and Events

If the EVM should run on a certain state an `@ethereumjs/statemanager` is needed. An `@ethereumjs/blockchain` instance can be passed in to provide access to external interface information like a blockhash:
If you want the EVM to run against a specific state, you need an `@ethereumjs/statemanager`. An `@ethereumjs/blockchain` instance can be passed in to provide access to external interface information like a blockhash:

```ts
// ./examples/withBlockchain.ts
Expand Down Expand Up @@ -121,16 +121,16 @@ const main = async () => {
void main()
```

Additionally this usage example shows the use of events to listen on the inner workings and procedural updates
Additionally, this example shows how to use events to listen to the inner workings and procedural updates
(`step` event) of the EVM.

### WASM Crypto Support

This library by default uses JavaScript implementations for the basic standard crypto primitives like hashing or signature verification (for included txs). See `@ethereumjs/common` [README](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/common) for instructions on how to replace with e.g. a more performant WASM implementation by using a shared `common` instance.
This library by default uses JavaScript implementations for the basic standard crypto primitives like hashing or signature verification (for included txs). See `@ethereumjs/common` [README](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/common) for instructions on how to replace them with, e.g., a more performant WASM implementation by using a shared `common` instance.

## Examples

See the [examples](./examples/) folder for different meaningful examples on how to use the EVM package and invoke certain aspects of it, e.g. running a bytecode snippet, listening to events or activate an EVM with a certain EIP for experimental purposes.
See the [examples](./examples/) folder for different meaningful examples on how to use the EVM package and invoke certain aspects of it, e.g. running a bytecode snippet, listening to events, or to activate an EVM with a certain EIP for experimental purposes.

## Browser

Expand All @@ -148,7 +148,7 @@ For documentation on `EVM` instantiation, exposed API and emitted `events` see g

With the breaking releases from Summer 2023 we have started to ship our libraries with both CommonJS (`cjs` folder) and ESM builds (`esm` folder), see `package.json` for the detailed setup.

If you use an ES6-style `import` in your code files from the ESM build will be used:
If you use an ES6-style `import` in your code files, the ESM build will be used:

```ts
import { EthereumJSClass } from '@ethereumjs/[PACKAGE_NAME]'
Expand All @@ -166,7 +166,7 @@ Using ESM will give you additional advantages over CJS beyond browser usage like

### VM/EVM Relation

This package contains the inner Ethereum Virtual Machine core functionality which was included in the [@ethereumjs/vm](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/vm) package up till v5 and has been extracted along the v6 release.
This package contains the inner Ethereum Virtual Machine core functionality which was included in the [@ethereumjs/vm](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/vm) package up to v5 and has been extracted along the v6 release.

This will make it easier to customize the inner EVM, which can now be passed as an optional argument to the outer `VM` instance.

Expand Down Expand Up @@ -277,7 +277,7 @@ This library supports the blob transaction type introduced with [EIP-4844](https

## Precompiles

This library support all EVM precompiles up to the `Prague` hardfork.
This library supports all EVM precompiles up to the `Prague` hardfork.

In our `examples` folder we provide a helper function for simple direct precompile runs in the `precompiles` folder.

Expand Down Expand Up @@ -378,7 +378,7 @@ You can subscribe to the following events:
#### Event listeners

You can perform asynchronous operations from within an event handler
and prevent the EVM to keep running until they finish.
and prevent the EVM from continuing until they finish.

If subscribing to events with an async listener, specify the second
parameter of your listener as a `resolve` function that must be called once your listener code has finished.
Expand All @@ -405,7 +405,7 @@ recommended not to do that.

## Understanding the EVM

If you want to understand your EVM runs we have added a hierarchically structured list of debug loggers for your convenience which can be activated in arbitrary combinations. We also use these loggers internally for development and testing. These loggers use the [debug](https://github.com/visionmedia/debug) library and can be activated on the CL with `DEBUG=ethjs,[Logger Selection] node [Your Script to Run].js` and produce output like the following:
If you want to understand your EVM runs we have added a hierarchically structured list of debug loggers for your convenience which can be activated in arbitrary combinations. We also use these loggers internally for development and testing. These loggers use the [debug](https://github.com/visionmedia/debug) library and can be activated on the CLI with `DEBUG=ethjs,[Logger Selection] node [Your Script to Run].js` and produce output like the following:

![EthereumJS EVM Debug Logger](./debug.png?raw=true)

Expand All @@ -420,7 +420,7 @@ The following loggers are currently available:
| `evm:ops` | Opcode traces |
| `evm:ops:[Lower-case opcode name]` | Traces on a specific opcode |

Here are some examples for useful logger combinations.
Here are some examples of useful logger combinations.

Run one specific logger:

Expand Down Expand Up @@ -515,9 +515,9 @@ This layered architecture provides separation of concerns while allowing for the

## Profiling the EVM

The EthereumJS EVM comes with build-in profiling capabilities to detect performance bottlenecks and to generally support the targeted evolution of the JavaScript EVM performance.
The EthereumJS EVM comes with built-in profiling capabilities to detect performance bottlenecks and to generally support the targeted evolution of the JavaScript EVM performance.

While the EVM has a dedicated `profiler` setting to activate, the profiler can best and most useful be run through the EthereumJS [client](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/client) since this gives the most realistic conditions providing both real-world txs and a meaningful state size.
While the EVM has a dedicated `profiler` setting to activate, the profiler is most useful when run through the EthereumJS [client](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/client) since this gives the most realistic conditions providing both real-world txs and a meaningful state size.

To repeatedly run the EVM profiler within the client sync the client on mainnet or a larger testnet to the desired block. Then the profiler should be run without sync (to not distort the results) by using the `--executeBlocks` and the `--vmProfileBlocks` (or `--vmProfileTxs`) flags in conjunction like:

Expand Down
6 changes: 3 additions & 3 deletions packages/evm/src/eof/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ class StreamReader {

/**
* Same as readUint, except this reads an uint16
* @param errorStr
* @returns
* @param errorStr - Optional error message to include in thrown validation errors
* @returns Unsigned 16-bit integer read from the stream
*/
readUint16(errorStr?: string) {
const end = this.ptr + 2
Expand Down Expand Up @@ -471,7 +471,7 @@ export class EOFContainer {
* @param evm EVM, to read opcodes from
* @param containerMode Container mode to validate on
* @param eofMode EOF mode to run in
* @returns
* @returns The decoded EOF container
*/
export function validateEOF(
input: Uint8Array,
Expand Down
31 changes: 30 additions & 1 deletion packages/evm/src/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,22 @@ const debug = debugDefault('evm:evm')
const debugGas = debugDefault('evm:gas')
const debugPrecompiles = debugDefault('evm:precompiles')

/**
* Creates a standardized ExecResult for out-of-gas errors.
* @param gasLimit - Gas limit consumed by the failing frame
* @returns Execution result describing the OOG failure
*/
export function OOGResult(gasLimit: bigint): ExecResult {
return {
returnValue: new Uint8Array(0),
executionGasUsed: gasLimit,
exceptionError: new EVMError(EVMError.errorMessages.OUT_OF_GAS),
}
}
// CodeDeposit OOG Result
/**
* Creates an ExecResult for code-deposit out-of-gas errors (EIP-3541).
* @param gasUsedCreateCode - Gas consumed while attempting to store code
*/
export function COOGResult(gasUsedCreateCode: bigint): ExecResult {
return {
returnValue: new Uint8Array(0),
Expand All @@ -75,6 +83,10 @@ export function COOGResult(gasUsedCreateCode: bigint): ExecResult {
}
}

/**
* Returns an ExecResult signalling invalid bytecode input.
* @param gasLimit - Gas consumed up to the point of failure
*/
export function INVALID_BYTECODE_RESULT(gasLimit: bigint): ExecResult {
return {
returnValue: new Uint8Array(0),
Expand All @@ -83,6 +95,10 @@ export function INVALID_BYTECODE_RESULT(gasLimit: bigint): ExecResult {
}
}

/**
* Returns an ExecResult signalling invalid EOF formatting.
* @param gasLimit - Gas consumed up to the point of failure
*/
export function INVALID_EOF_RESULT(gasLimit: bigint): ExecResult {
return {
returnValue: new Uint8Array(0),
Expand All @@ -91,6 +107,10 @@ export function INVALID_EOF_RESULT(gasLimit: bigint): ExecResult {
}
}

/**
* Returns an ExecResult for code size violations.
* @param gasUsed - Gas consumed before the violation was detected
*/
export function CodesizeExceedsMaximumError(gasUsed: bigint): ExecResult {
return {
returnValue: new Uint8Array(0),
Expand All @@ -99,6 +119,11 @@ export function CodesizeExceedsMaximumError(gasUsed: bigint): ExecResult {
}
}

/**
* Wraps an {@link EVMError} in an ExecResult.
* @param error - Error encountered during execution
* @param gasUsed - Gas consumed up to the error
*/
export function EVMErrorResult(error: EVMError, gasUsed: bigint): ExecResult {
return {
returnValue: new Uint8Array(0),
Expand All @@ -107,6 +132,10 @@ export function EVMErrorResult(error: EVMError, gasUsed: bigint): ExecResult {
}
}

/**
* Creates a default block header used by stand-alone executions.
* @returns Block-like object with zeroed header fields
*/
export function defaultBlock(): Block {
return {
header: {
Expand Down
17 changes: 9 additions & 8 deletions packages/evm/src/precompiles/bls12_381/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ const ZERO_BYTES_16 = new Uint8Array(16)
* 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.
*
* @param numPairs
* @param gasUsedPerPair
* @returns
* @param numPairs - Number of pairings provided to the precompile
* @param gasUsedPerPair - Base gas cost per pairing
* @param discountTable - Discount table (pair count -> multiplier)
* @returns Total gas to charge after applying the discount table
*/
export const msmGasUsed = (
numPairs: number,
Expand Down Expand Up @@ -49,11 +50,11 @@ export const msmGasUsed = (
* ]
* ```
*
* @param opts
* @param zeroByteRanges
* @param pName
* @param pairStart
* @returns
* @param opts - Precompile input wrapper containing the data to inspect
* @param zeroByteRanges - Ranges (as [start, end]) within which bytes must be zero
* @param pName - Human readable precompile name for logging
* @param pairStart - Optional offset into the data when iterating through pairs
* @returns `true` if every specified range contains only zero bytes
*/
export const leading16ZeroBytesCheck = (
opts: PrecompileInput,
Expand Down
24 changes: 12 additions & 12 deletions packages/evm/src/precompiles/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import type { PrecompileInput } from './index.ts'
/**
* Checks that the gas used remain under the gas limit.
*
* @param opts
* @param gasUsed
* @param pName
* @returns
* @param opts - Precompile input wrapper
* @param gasUsed - Amount of gas consumed by the precompile
* @param pName - Human readable precompile name for logging
* @returns `true` if the gas usage is within the provided limit
*/
export const gasLimitCheck = (opts: PrecompileInput, gasUsed: bigint, pName: string) => {
if (opts._debug !== undefined) {
Expand All @@ -31,10 +31,10 @@ export const gasLimitCheck = (opts: PrecompileInput, gasUsed: bigint, pName: str
/**
* Checks that the length of the provided data is equal to `length`.
*
* @param opts
* @param length
* @param pName
* @returns
* @param opts - Precompile input wrapper
* @param length - Required data length in bytes
* @param pName - Human readable precompile name for logging
* @returns `true` if the provided data matches the required length
*/
export const equalityLengthCheck = (opts: PrecompileInput, length: number, pName: string) => {
if (opts.data.length !== length) {
Expand All @@ -52,10 +52,10 @@ export const equalityLengthCheck = (opts: PrecompileInput, length: number, pName
* Checks that the total length of the provided data input can be subdivided into k equal parts
* with `length` (without leaving some remainder bytes).
*
* @param opts
* @param length
* @param pName
* @returns
* @param opts - Precompile input wrapper
* @param length - Required chunk size
* @param pName - Human readable precompile name for logging
* @returns `true` if the length is divisible by the chunk size
*/
export const moduloLengthCheck = (opts: PrecompileInput, length: number, pName: string) => {
if (opts.data.length % length !== 0) {
Expand Down
32 changes: 29 additions & 3 deletions packages/evm/src/stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export class Stack {
return this._len
}

/**
* Pushes a new bigint onto the stack.
* @param value - Value to push (must fit within the configured max height)
*/
push(value: bigint) {
if (this._len >= this._maxHeight) {
throw new EVMError(EVMError.errorMessages.STACK_OVERFLOW)
Expand All @@ -30,6 +34,10 @@ export class Stack {
this._store[this._len++] = value
}

/**
* Pops the top value from the stack.
* @returns The value removed from the stack
*/
pop(): bigint {
if (this._len < 1) {
throw new EVMError(EVMError.errorMessages.STACK_UNDERFLOW)
Expand All @@ -47,6 +55,11 @@ export class Stack {
* in returned array.
* @param num - Number of items to pop
*/
/**
* Pops multiple items from the stack with the top-most item returned first.
* @param num - Number of items to pop (defaults to 1)
* @returns Array containing the popped values
*/
popN(num: number = 1): bigint[] {
if (this._len < num) {
throw new EVMError(EVMError.errorMessages.STACK_UNDERFLOW)
Expand All @@ -72,6 +85,11 @@ export class Stack {
* @param num Number of items to return
* @throws {@link ERROR.STACK_UNDERFLOW}
*/
/**
* Returns items from the stack without removing them.
* @param num - Number of items to return (defaults to 1)
* @returns Array of items, with index 0 representing the top of the stack
*/
peek(num: number = 1): bigint[] {
const peekArray: bigint[] = Array(num)
let start = this._len
Expand All @@ -90,6 +108,10 @@ export class Stack {
* Swap top of stack with an item in the stack.
* @param position - Index of item from top of the stack (0-indexed)
*/
/**
* Swaps the top of the stack with another item.
* @param position - Zero-based index from the top of the stack (0 swaps with the top itself)
*/
swap(position: number) {
if (this._len <= position) {
throw new EVMError(EVMError.errorMessages.STACK_UNDERFLOW)
Expand All @@ -112,6 +134,10 @@ export class Stack {
// since you can't copy a primitive data type
// Nevertheless not sure if we "loose" something here?
// Will keep commented out for now
/**
* Pushes a copy of an item deeper in the stack.
* @param position - One-based index of the item to duplicate
*/
dup(position: number) {
const len = this._len
if (len < position) {
Expand All @@ -128,9 +154,9 @@ export class Stack {
}

/**
* Swap number 1 with number 2 on the stack
* @param swap1
* @param swap2
* Swaps two arbitrary entries relative to the top of the stack.
* @param swap1 - Distance from the top (0 = top element) for the first entry
* @param swap2 - Distance from the top for the second entry
*/
exchange(swap1: number, swap2: number) {
const headIndex = this._len - 1
Expand Down
2 changes: 1 addition & 1 deletion packages/evm/src/stemCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class StemCache {

/**
* Returns the size of the cache
* @returns
* @returns Number of cached stems currently stored
*/
size() {
return this.cache.size
Expand Down
Loading