Nethereum.EVM 7.0.0

Prefix Reserved
dotnet add package Nethereum.EVM --version 7.0.0
                    
NuGet\Install-Package Nethereum.EVM -Version 7.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.

                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.

                    
Directory.Packages.props

                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Nethereum.EVM --version 7.0.0
                    
#r "nuget: Nethereum.EVM, 7.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Nethereum.EVM@7.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Nethereum.EVM&version=7.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Nethereum.EVM&version=7.0.0
                    
Install as a Cake Tool

Nethereum.EVM

Run Ethereum bytecode in your own process. Point it at a node and it replays a transaction against real chain state; point it at a dictionary and it runs a contract with no node at all. Either way you get the gas, the logs, the storage writes, the revert reason and a step-by-step trace — without a tracing node, and without broadcasting anything.

What you can do with it

  • Preview a transaction before a user signs it — will it succeed, what will it cost, and who ends up with what.
  • Explain a transaction that already happened — replay it against archive state and read the decoded call tree.
  • Get a debug_traceTransaction-style trace from any node — the trace is produced locally, so the node only has to answer ordinary state reads: eth_getBalance, eth_getCode, eth_getStorageAt, eth_getTransactionCount and eth_getBlockByNumber, plus eth_getProof and debug_storageRangeAt on the two paths listed under RpcNodeDataService below.
  • See who gained and lost what — ETH, ERC-20, ERC-721 and ERC-1155 movements, cross-checked against the balances the execution actually observed.
  • Decode a result into calls, logs and custom errors, including the inner call tree and the revert reason.
  • Step through Solidity source — breakpoints by file and line, stack, memory and storage at every step.
  • Disassemble deployed bytecode and ask which function selectors a contract implements.
  • Replay a whole block and check the gas, receipts and post-state root.
  • Execute under any fork's rules, from Frontier to Amsterdam, chosen per block rather than per process.

Quick start

Simulate a transaction and read the result.

From EvmSimulatorDocExampleTests.QuickStart_SimulateATransactionAndReadTheResult (use case quick-start) — a tagged, passing test.

IStateReader state = new InMemoryStateReader(new Dictionary
{
    [SenderAddress] = new AccountState { Balance = EvmUInt256.Parse("1000000000000000000") },
    [ContractAddress] = new AccountState { Code = "600760005500".HexToByteArray() }
});

var result = await new TransactionExecutor(DefaultHardforkConfigs.Cancun).ExecuteAsync(
    new TransactionExecutionContext
    {
        Sender = SenderAddress,
        To = ContractAddress,
        Data = new byte[0],
        GasLimit = 1_000_000,
        GasPrice = 1,
        ChainId = 1,
        BlockNumber = 19_426_587,
        Timestamp = MainnetChainActivations.CancunTimestamp,
        Coinbase = SenderAddress,
        ExecutionState = new ExecutionStateService(state)
    });

Assert.True(result.Success, result.Error);
Assert.True(result.GasUsed > 21_000);
Assert.Empty(result.Logs);

state is the only line that changes when you move to a real chain — swap the in-memory reader for the RPC-backed one and the same simulation runs against live mainnet state. That reader needs a node, so its constructor is shown as a declaration rather than a runnable snippet; it is the tagged RpcNodeDataService (Nethereum.EVM.BlockchainState), whose full surface is below.

public RpcNodeDataService(IEthApiService ethApiService, BlockParameter currentBlock)

Entry points

Start with TransactionExecutor. It is what you want for “what would this transaction do?” — it handles intrinsic gas, validation, EIP-7702 setup, execution and refunds. Reach past it only when you want a single frame (EVMSimulator) or a whole block (BlockExecutor).

I want to… Reach for
Simulate one transaction new TransactionExecutor(config).ExecuteAsync(ctx)
Run raw bytecode, one frame new EVMSimulator(config).ExecuteWithCallStackAsync(program)
Execute a whole block BlockExecutor.ExecuteAsync(block, encodingProvider, registry, …)
Read state from a live node new RpcNodeDataService(web3.Eth, blockParameter)
Get the rules for a fork DefaultHardforkConfigs.Cancun, or DefaultMainnetHardforkRegistry.Instance.Get(fork)
Turn a result into calls, logs and errors new ProgramResultDecoder(abiStorage).Decode(…)
See who gained and lost what new StateChangesExtractor().ExtractFromDecodedResultAsync(decoded, …)
Step through Solidity source program.CreateDebugSession(abiStorage, chainId)
Disassemble deployed bytecode ProgramInstructionsUtils.GetProgramInstructions(byteCode)

HardforkConfig is the bundle of rules for one fork — opcode handlers, gas rules, precompiles. Every executor takes one. DefaultHardforkConfigs exposes presets from Frontier through Osaka, and DefaultHardforkConfigs.Default is Osaka — not the newest fork the engine knows. Amsterdam, the last entry in HardforkName and in HardforkSpecRegistry.All, has no DefaultHardforkConfigs preset: reach it through DefaultMainnetHardforkRegistry.Instance.Get(HardforkName.Amsterdam).

Installation

dotnet add package Nethereum.EVM

BLS12-381 precompiles (0x0b–0x11) and KZG point evaluation (0x0a) are not in the box — add Nethereum.EVM.Precompiles.Bls or Nethereum.EVM.Precompiles.Kzg and call WithBlsBackend / WithKzgBackend if you need them. Nethereum.CoreChain adds the Patricia state- and block-root calculators.

Running bytecode

Every runnable snippet below is extracted from a [NethereumDocExample(DocSection.EvmSimulator, …)]-tagged passing test in tests/Nethereum.EVM.UnitTests.

Execute raw bytecode

From EvmSimulatorDocExampleTests.RunRawBytecodeAndReadTheStack (use case run-bytecode).

var simulator = new EVMSimulator(DefaultHardforkConfigs.Cancun);
var program = new Program("6002600301".HexToByteArray());

await simulator.ExecuteWithCallStackAsync(program, traceEnabled: false);

Assert.Equal(
    "0000000000000000000000000000000000000000000000000000000000000005",
    program.StackPeek().ToHex());

Program holds the bytecode, the stack, the memory, the gas counters and the accumulated trace. EVMSimulator drives it. The bytecode above is PUSH1 2; PUSH1 3; ADD.

DefaultHardforkConfigs (from the source-linked Nethereum.EVM.Precompiles) exposes a ready HardforkConfig per fork — Frontier … Osaka, plus Default (currently Osaka) — each already carrying its precompile registry.

Trace every step

Tracing needs a ProgramContext: the trace records the executing contract and code address, so a bare new Program(bytecode) cannot be traced.

From EvmSimulatorDocExampleTests.ReadThePerStepTrace (use case run-bytecode).

var context = new ProgramContext(
    new EvmCallContext { From = SenderAddress, To = ContractAddress, Data = new byte[0], Gas = 1_000_000 },
    new ExecutionStateService(new InMemoryStateReader(new Dictionary())));

var simulator = new EVMSimulator(DefaultHardforkConfigs.Cancun);
var program = new Program("6002600301".HexToByteArray(), context);

await simulator.ExecuteWithCallStackAsync(program, traceEnabled: true);

var steps = program.Trace;
Assert.Equal(Instruction.PUSH1, steps[0].Instruction.Instruction);
Assert.Equal(3, steps[0].GasCost);
Assert.Equal(Instruction.ADD, steps[2].Instruction.Instruction);
Assert.Equal(9, program.TotalGasUsed);

Each ProgramTrace carries ProgramAddress, CodeAddress, VMTraceStep, ProgramTraceStep, Depth, Instruction, Stack, Memory / MemoryAsArray, Storage, GasCost, GasRemaining and OutOfGas.

Disassemble

From EvmSimulatorDocExampleTests.DisassembleDeployedBytecode (use case disassemble-bytecode).

var instructions = ProgramInstructionsUtils.GetProgramInstructions("60806040523415");

Assert.Equal("0000   60   PUSH1  0x80", instructions[0].ToDisassemblyLine());
Assert.Equal(Instruction.MSTORE, instructions[2].Instruction);
Assert.Equal(Instruction.CALLVALUE, instructions[3].Instruction);

var listing = ProgramInstructionsUtils.DisassembleToString(instructions);
Assert.Contains("MSTORE", listing);

ProgramInstructionsUtils also offers GetProgramInstructions(byte[] byteCodeArray), the bool eip8024Enabled overloads, DisassembleToString(string byteCode), DisassembleSimplifiedToString(string byteCode), and the dispatcher helpers GetFunctionDispatcherMap(...), ContainsFunctionSignature(List instructions, string signature) and ContainsFunctionSignatures(List instructions, string[] signatures) — enough to answer "does this deployed contract implement transfer(address,uint256)?" from bytecode alone.

Fork rules and precompiles

Resolve the fork, then look up its rules

From EvmSimulatorDocExampleTests.ResolveTheForkForABlockAndLookUpItsRules (use case hardfork-config).

var fork = MainnetChainActivations.Instance.ResolveAt(
    blockNumber: 19_426_587, timestamp: MainnetChainActivations.CancunTimestamp);

Assert.Equal(HardforkName.Cancun, fork);

var config = DefaultMainnetHardforkRegistry.Instance.Get(fork);

Assert.Equal(GasConstants.MAX_CODE_SIZE, config.MaxCodeSize);
Assert.True(config.BaseFeeApplies);

DefaultMainnetHardforkRegistry.Instance is MainnetHardforkRegistry.Build(DefaultPrecompileBackends.Instance) — every mainnet fork, wired with the default crypto. HardforkRegistry.Get throws for HardforkName.Unspecified and for an unregistered fork; it never falls back to a default.

For a non-mainnet chain, register its activation table first — an unknown chain id is refused rather than silently replayed under mainnet rules:

From EvmSimulatorDocExampleTests.AnUnregisteredChainIdIsRefused (use case hardfork-config).

var registry = new ChainActivationsRegistry();

Assert.Equal(
    HardforkName.Cancun,
    registry.ResolveAt(chainId: 1, blockNumber: 19_426_587, timestamp: MainnetChainActivations.CancunTimestamp));

Assert.Throws(
    () => registry.ResolveAt(chainId: 424242, blockNumber: 1, timestamp: 1));

The precompile registry

A HardforkConfig static preset carries no precompiles — HardforkConfig.Cancun.Precompiles is null. WithPrecompiles clones the config and attaches a registry, so the shared preset is never mutated.

From EvmSimulatorDocExampleTests.AttachAPrecompileRegistryToAHardforkConfig (use case precompiles).

Assert.Null(HardforkConfig.Cancun.Precompiles);

var config = HardforkConfig.Cancun.WithPrecompiles(DefaultPrecompileRegistries.CancunBase());

Assert.True(config.Precompiles.CanHandle(0x01));
Assert.True(config.Precompiles.CanHandle(0x09));
Assert.False(config.Precompiles.CanHandle(0x0b));

DefaultHardforkConfigs.Cancun is the same thing done for you.

Gas is a separate concern from execution, so you can price a precompile call without running it:

From EvmSimulatorDocExampleTests.QueryPrecompileGasAndExecuteAHandler (use case precompiles).

var registry = DefaultPrecompileRegistries.OsakaBase();

Assert.Equal(6900L, registry.GetGasCost(0x100, new byte[160]));

var identity = registry.Get(0x04);
Assert.Equal(0x04, identity.AddressNumeric);
Assert.Equal(new byte[] { 0x01, 0x02, 0x03 }, registry.Execute(0x04, new byte[] { 0x01, 0x02, 0x03 }));
Registry factory (DefaultPrecompileRegistries)
FrontierBase(), ByzantiumBase(), IstanbulBase(), BerlinBase(), CancunBase(), PragueBase(), OsakaBase() fork-shaped handler sets and gas schedules
.WithBlsBackend(IBls12381Operations) (Nethereum.EVM.Precompiles.Bls) installs 0x0b–0x11
.WithKzgBackend() (Nethereum.EVM.Precompiles.Kzg) installs 0x0a

The same two extensions exist on HardforkConfig, so HardforkConfig.Prague.WithPrecompiles(DefaultPrecompileRegistries.PragueBase()).WithBlsBackend(ops) composes in one expression.

Simulating a transaction

TransactionExecutor runs one transaction end to end — intrinsic gas, validation rules, EIP-7702 setup, execution, refunds, receipt construction — against any IStateReader.

From EvmSimulatorDocExampleTests.SimulateATransactionAgainstInMemoryState (use case simulate-transaction).

var accounts = new Dictionary
{
    [SenderAddress] = new AccountState { Balance = EvmUInt256.Parse("1000000000000000000") },
    [ContractAddress] = new AccountState { Code = "600760005500".HexToByteArray() }
};
var stateReader = new InMemoryStateReader(accounts);

var ctx = new TransactionExecutionContext
{
    Sender = SenderAddress,
    To = ContractAddress,
    Data = new byte[0],
    GasLimit = 1_000_000,
    GasPrice = 1,
    Nonce = 0,
    BlockNumber = 19_426_587,
    Timestamp = MainnetChainActivations.CancunTimestamp,
    Coinbase = SenderAddress,
    ChainId = 1,
    ExecutionState = new ExecutionStateService(stateReader)
};

var executor = new TransactionExecutor(DefaultMainnetHardforkRegistry.Instance.Get(HardforkName.Cancun));
var result = await executor.ExecuteAsync(ctx);

Assert.True(result.Success, result.Error);
Assert.Equal(TransactionError.None, result.ErrorCode);
Assert.True(result.GasUsed > 21_000);

Failure is reported in two different places

TransactionExecutionResult.ErrorCode is a TransactionError, and it reports pre-execution validation failures only:

public enum TransactionError
{
    None,
    InsufficientMaxFeePerGas,
    PriorityGreaterThanMaxFee,
    InsufficientBalance,
    GasAllowanceExceeded,
    IntrinsicGasTooLow,
    NonceIsMax,
    SenderNotEOA,
    InitcodeSizeExceeded,
    Type3TxContractCreation,
    Type3TxZeroBlobs,
    Type3TxBlobCountExceeded,
    Type3TxInvalidBlobVersionedHash,
    AddressCollision,
    InvalidEFPrefix,
    MaxCodeSizeExceeded,
    OutOfGas,
    Reverted,
    InsufficientMaxFeePerBlobGas,
    GasLimitExceedsMaximum,
    Type4TxContractCreation,
    Type4EmptyAuthorizationList,
    TransactionTypeNotSupported,
    InvalidChainId,
    NonceMismatch,
}

An in-EVM REVERT is not one of them — and Reverted is declared but never assigned, so check ProgramResult.IsRevert, not the code:

From EvmSimulatorDocExampleTests.AnInEvmRevertFailsTheTransactionWithoutSettingAnErrorCode (use case simulate-transaction).

Assert.False(result.Success);
Assert.True(result.ProgramResult.IsRevert);
Assert.Equal(TransactionError.None, result.ErrorCode);
Assert.False(result.IsValidationError);

So: check Success first; then IsValidationError / ErrorCode for a rejected transaction, or ProgramResult.IsRevert and RevertReason for a reverted one.

TransactionExecutionResult also carries Error — the failure message the snippets above pass to Assert.True(result.Success, result.Error) — along with GasUsed, GasRefund, EffectiveGasUsed, ExecutionGasUsed, StateGasUsed, ReturnData, Logs, StateRoot, ContractAddress, Traces, Program, CreatedAccounts, DeletedAccounts, InnerCalls and InnerContractCodeCalls.

Simulating against a live chain: RpcNodeDataService

RpcNodeDataService : IStateReader, IAccountStorageReader is the host-only state reader that answers every read from a node over JSON-RPC.

public class RpcNodeDataService : IStateReader, IAccountStorageReader
{
    public RpcNodeDataService(IEthApiService ethApiService, BlockParameter currentBlock);

    public RpcNodeDataService(
        IEthApiService ethApiService,
        BlockParameter currentBlock,
        IDebugApiService debugApiService,
        string blockHash,
        int transactionIndex,
        bool useDebugStorageAt = true);

    public IEthApiService EthApiService { get; }
    public BlockParameter CurrentBlock { get; }
    public IDebugApiService DebugApiService { get; }
    public string BlockHash { get; }
    public int TransactionIndex { get; }
    public bool UseDebugStorageAt { get; }

    public Task AccountHasStorageAsync(string address);
    public static bool StorageHashIndicatesStorage(string storageHash);
}

Each read maps to a JSON-RPC call, and the set is wider than the four core account reads:

Member JSON-RPC issued
GetBalanceAsync eth_getBalance
GetCodeAsync eth_getCode
GetStorageAtAsync eth_getStorageAt, preceded by debug_storageRangeAt when UseDebugStorageAt is set
GetTransactionCountAsync eth_getTransactionCount
GetBlockHashAsync eth_getBlockByNumber
AccountExistsAsync eth_getBalance, then eth_getTransactionCount, then eth_getCode
AccountHasStorageAsync eth_getProof

eth_getProof is the one read a node may not serve — Erigon in particular does not. AccountHasStorageAsync catches the failure and returns false, so the simulation degrades rather than throwing — but on such a node the answer is "no storage", not the truth.

Point it at web3.Eth, hand it to an ExecutionStateService, and the simulator replays a transaction against real mainnet state without a local node database. The second constructor is the interesting one: it reads state mid-block, at a given transaction index, using debug_storageRangeAt. That is what makes “what would this transaction have done at position 7 of block N?” answerable.

Decoding a result

Raw EVM output is bytes. ProgramResultDecoder turns a finished ProgramResult into a call tree, decoded logs, decoded return values and a decoded revert, using ABIs from an IABIInfoStorage (Nethereum.ABI.ABIRepository).

Member
ProgramResultDecoder(IABIInfoStorage abiStorage) constructor
DecodedProgramResult Decode(...) three overloads over a program result / call
DecodedCall DecodeCall(CallInput call, BigInteger chainId, int depth) one call frame
DecodedLog DecodeLog(FilterLog log, BigInteger chainId) one log
DecodedError DecodeRevert(byte[] revertData, BigInteger chainId, string contractAddress) revert data → error
List DecodeReturnValue(FunctionABI functionABI, string output) return values
Result type Members
DecodedProgramResult RootCall, DecodedLogs, ReturnValue, RevertReason, IsRevert, IsSuccess, OriginalResult, OriginalCall, ChainId, ToHumanReadableString()
DecodedCall From, To, ContractName, Function, InputParameters, OutputParameters, InnerCalls, Logs, CallType, Depth, IsDecoded, RawInput, RawOutput, Value, GasUsed, IsRevert, Error, OriginalCall, GetFunctionSignature(), GetFunctionName(), GetDisplayName()
DecodedLog ContractAddress, ContractName, Event, Parameters, IsDecoded, OriginalLog, LogIndex, CallDepth, GetEventSignature(), GetEventName(), GetDisplayName()
DecodedError Error, Parameters, Message, IsStandardError, IsDecoded, RawData, GetErrorSignature(), GetErrorName(), GetDisplayMessage(), FromStandardError(string message, string rawData = null), FromUnknownError(string rawData)

Each frame records how it was entered:

public enum CallType
{
    Call,
    DelegateCall,
    StaticCall,
    CallCode,
    Create,
    Create2
}

IsDecoded is false when no ABI was found — the frame still shows with its raw calldata, so an unknown contract does not break the tree.

Extracting state changes

StateChangesExtractor : IStateChangesExtractor reads a DecodedProgramResult and produces "what actually moved" — the answer a wallet needs before asking a user to sign.

StateChangesResult ExtractFromDecodedResult(
    DecodedProgramResult decodedResult,
    ExecutionStateService stateService = null,
    string currentUserAddress = null);

StateChangesResult ExtractFromDecodedResult(
    DecodedProgramResult decodedResult,
    ExecutionStateService stateService,
    string currentUserAddress,
    Func tokenResolver);

Task ExtractFromDecodedResultAsync(
    DecodedProgramResult decodedResult,
    ExecutionStateService stateService = null,
    string currentUserAddress = null,
    Func> tokenResolverAsync = null,
    CancellationToken cancellationToken = default);

It recognises transfers by event signature — the public constants TRANSFER_EVENT_SIGNATURE, TRANSFER_SINGLE_EVENT_SIGNATURE and TRANSFER_BATCH_EVENT_SIGNATURE cover ERC-20/721 Transfer, ERC-1155 TransferSingle and TransferBatch — and the tokenResolver you supply turns a token address into a TokenInfo { Symbol, Decimals } for display.

Type Members
StateChangesResult BalanceChanges, RootCall, DecodedLogs, DecodedResult, Error, Traces, GasUsed, HasError, HasBalanceChanges, HasDecodedLogs, HasTraces, ToSummaryString()
BalanceChange Address, AddressLabel, IsCurrentUser, Type, TokenAddress, TokenSymbol, TokenDecimals, TokenId, Change, BalanceBefore, BalanceAfter, ActualChange, ActualOwner, ValidationStatus, HasDiscrepancy, GetTokenIdentifier(), GetDisplaySymbol(), GetAddressDisplay()
public enum BalanceChangeType
{
    Native,
    ERC20,
    ERC721,
    ERC1155
}

public enum BalanceValidationStatus
{
    NotValidated,
    Verified,
    FeeOnTransfer,
    Rebasing,
    OwnerMismatch,
    Mismatch
}

public class TokenInfo
{
    public string Symbol { get; set; }
    public int Decimals { get; set; }

    public TokenInfo();
    public TokenInfo(string symbol, int decimals);
}

ValidateTokenBalances(...) cross-checks the transfer events against the balances actually observed in the execution state, but it only validates a token type when you pass the matching resolver delegate (getErc20Balance, getErc721Owner, getErc1155Balance); otherwise the change stays NotValidated. That is what surfaces FeeOnTransfer and Rebasing: the token emitted a Transfer for X but the recipient's balance moved by something else. HasDiscrepancy is the one flag a UI should never hide.

Source-level debugging

EVMDebuggerSession replays a trace with Solidity source mapping — the same experience as a step debugger, over a simulated transaction.

Area Members
Construction / loading EVMDebuggerSession(IABIInfoStorage abiStorage); LoadFromProgram(Program executedProgram, BigInteger chainId), LoadFromProgramAsync, LoadFromTrace(List trace, BigInteger chainId), LoadFromTraceAsync
Navigation Trace, CurrentStep, TotalSteps, CanStepForward, CanStepBack, StepForward(), StepBack(), GoToStep(int step), GoToStart(), GoToEnd()
Current state CurrentTrace, CurrentInstruction, CurrentStack, CurrentMemory, CurrentStorage, CurrentDepth, CurrentGasCost, CurrentCodeAddress, CurrentProgramAddress
Source mapping GetCurrentSourceLocation(), GetSourceLocationForStep(int stepIndex), GetNearestSourceLocation(int stepIndex, int maxLookahead = 20), GetFunctionDeclarationLocation(string functionName, string codeAddress), FindStepsForSourceLine(string filePath, int lineNumber)
Call decoding GetFunctionNameForStep(int stepIndex), GetCurrentContractName(), GetCallInfoForStep(int stepIndex) → CallStepInfo
Debug info SetContractDebugInfo(string address, ABIInfo abiInfo), GetABIInfoForAddress(string address), GetContractNameForAddress(string address), GetSourceFiles(), GetAllSourceFileContents()
Rendering ToDebugString(), ToSummaryString()

FindStepsForSourceLine is the breakpoint primitive: give it a file and a line and it returns every trace step that maps there. GetCallInfoForStep answers “which call am I standing in?”:

public class CallStepInfo
{
    public string TargetAddress { get; set; }
    public string ContractName { get; set; }
    public string CallType { get; set; }
    public string Selector { get; set; }
    public string FunctionName { get; set; }
    public string FunctionSignature { get; set; }
    public List DecodedInputs { get; set; }
    public string RawCalldata { get; set; }
}

EVMDebuggerExtensions adds the convenience layer: program.CreateDebugSession(abiStorage, chainId) and trace.CreateDebugSession(abiStorage, chainId) (both with …Async twins), GenerateFullTraceString(), GenerateSourceAnnotatedTrace(), GetUniqueSourceLocations(), HasDebugInfo(), EnumerateWithSource() → DebugStepInfo { Step, Trace, Source }, and StepToNextSourceLine() / StepToPreviousSourceLine() for stepping by Solidity line instead of by opcode.

A SourceLocation carries FilePath, Position, Length, SourceCode, FullFileContent, LineNumber, ColumnNumber, SourceFileIndex, JumpType, ModifierDepth, and GetContextLines(int linesBefore = 2, int linesAfter = 2) for rendering a snippet around the current line.

Worked debugger scenarios live in tests/Nethereum.EVM.UnitTests/EVMDebuggerTests.cs.

Bridging to the RPC types

EvmTypeConversions (Nethereum.EVM.Compatibility) converts between the engine's own types and the RPC DTOs — this is the seam that only exists in the host arm:

Extension
CallInput.ToEvmCallContext() / TransactionInput.ToEvmCallContext() RPC → engine
EvmCallContext.ToCallInput() / List.ToCallInputs() engine → RPC
EvmLog.ToFilterLog() / List.ToFilterLogs() engine → RPC

Blocks, requests and state gas

Block execution, witnesses, the EIP-7685 execution requests, the EIP-8037 state-gas dimension and the system-call policy live in the shared engine source, so they are in this assembly too. They are explained once, in the Nethereum.EVM.Core README — hardforks and the registry, state reading, execution requests, system calls, state gas and witnesses. Use BlockExecutor.ExecuteAsync(...) here where that page shows BlockExecutor.Execute(...); the parameters are identical.

The declarations you are most likely to reach for from this package:

public enum HardforkName
{
    Unspecified = 0,
    Frontier,
    FrontierThawing,
    Homestead,
    DaoFork,
    TangerineWhistle,
    SpuriousDragon,
    Byzantium,
    Constantinople,
    Petersburg,
    Istanbul,
    MuirGlacier,
    Berlin,
    London,
    ArrowGlacier,
    GrayGlacier,
    Paris,
    Shanghai,
    Cancun,
    Prague,
    Osaka,
    OsakaBpo1,
    OsakaBpo2,
    Amsterdam
}

public enum ExecutionMode
{
    Transaction,
    Call,
    SystemCall
}

Supply state by implementing one interface — ten methods and the engine runs:

public interface IStateReader
{
    Task GetBalanceAsync(byte[] address);
    Task GetBalanceAsync(string address);
    Task GetCodeAsync(byte[] address);
    Task GetCodeAsync(string address);
    Task GetStorageAtAsync(byte[] address, EvmUInt256 position);
    Task GetStorageAtAsync(string address, EvmUInt256 position);
    Task GetTransactionCountAsync(byte[] address);
    Task GetTransactionCountAsync(string address);
    Task AccountExistsAsync(string address);
    Task GetBlockHashAsync(long blockNumber);
}

From Prague a block also commits to its execution requests, and from Amsterdam gas has a second, state-growth dimension:

public static class ExecutionRequests
{
    public const byte DepositRequestType = 0x00;
    public const byte WithdrawalRequestType = 0x01;
    public const byte ConsolidationRequestType = 0x02;
    public const byte BuilderDepositRequestType = 0x03;
    public const byte BuilderExitRequestType = 0x04;

    public static byte RequestTypeFor(string predeployAddress);
    public static byte[] Compose(byte requestType, byte[] requestData);
    public static bool CarriesData(byte[] request);
    public static bool IsActive(HardforkName fork);
    public static byte[] CommitmentFor(HardforkName fork, IEnumerable blockRequests);
    public static byte[] ComputeRequestsHash(IEnumerable blockRequests);
}

public static class SystemCallContracts
{
    public const string BeaconRoots = "0x000f3df6d732807ef1319fb7b8bb8522d0beac02";
    public const string HistoryStorage = "0x0000F90827F1C53a10cb7A02335B175320002935";
    public const string WithdrawalRequests = "0x00000961Ef480Eb55e80D19ad83579A64c007002";
    public const string ConsolidationRequests = "0x0000BBdDc7CE488642fb579F8B00f3a590007251";
    public const string BuilderDeposit = "0x0000BFF46984E3725691FA540A8C7589300D8282";
    public const string BuilderExit = "0x000064D678505AD48F8CCB093BC65613800E8282";

    public const string SystemCaller = Nethereum.Util.AddressUtil.SYSTEM_ADDRESS;

    public static IReadOnlyList RequestContractsFor(HardforkName fork);

    public static readonly IReadOnlyList AllRequestContracts;
}

public sealed class StateGasAccount
{
    public long ReservoirRemaining { get; set; }
    public long FromReservoir { get; set; }
    public long SpilledIntoExecution { get; set; }
}

ExecutionRequests.IsActive(fork) is false before Prague — and a header with no requests_hash is a different thing from one carrying the hash of an empty list.

What lives only in this package

Namespace Types
Nethereum.EVM.BlockchainState RpcNodeDataService
Nethereum.EVM.Decoding ProgramResultDecoder, DecodedProgramResult, DecodedCall, DecodedLog, DecodedError, CallType
Nethereum.EVM.StateChanges IStateChangesExtractor, StateChangesExtractor, StateChangesResult, BalanceChange, BalanceChangeType, BalanceValidationStatus, TokenInfo
Nethereum.EVM.Debugging EVMDebuggerSession, EVMDebuggerExtensions, DebugStepInfo, CallStepInfo
Nethereum.EVM.Compatibility EvmTypeConversions

Everything else you can reach from this assembly is shared source from Nethereum.EVM.Core and Nethereum.EVM.Precompiles.

Relationship to Nethereum.EVM.Core

Nethereum.EVM and Nethereum.EVM.Core are one source tree compiled twice. This package is the asynchronous host build; Nethereum.EVM.Core is the same files compiled with EVM_SYNC defined, giving a synchronous, Task-free, AOT- and trim-safe engine for the Zisk zkVM guest. Practically, that means EVMSimulator, Program, TransactionExecutor, BlockExecutor, HardforkConfig and the precompile registry are all in this assembly — you never need a second package reference to reach them.

What differs is only the shape of the API. This package publishes the async half of each twinned entry point:

public async Task ExecuteWithCallStackAsync(Program program, int vmExecutionCounter = 0, int depth = 0, bool traceEnabled = true)

public async Task ExecuteAsync(TransactionExecutionContext ctx)

public static async Task ExecuteAsync(
    BlockWitnessData block,
    IBlockEncodingProvider encodingProvider,
    HardforkRegistry hardforkRegistry,
    IStateRootCalculator stateRootCalculator = null,
    IBlockRootCalculator blockRootCalculator = null)

Nethereum.EVM.Core publishes ExecuteWithCallStack, Execute and Execute instead, and ProgramResult.Logs is List here against List there.

The two builds must stay behaviourally identical, or a zk proof would attest to different rules than the node executed. tests/Nethereum.EVM.UnitTests/Execution/BlockExecutorArmsAgreeTests.cs enforces that: it reads BlockExecutor.cs, splits it at its preprocessor directives and diffs the two arms statement by statement, permitting exactly one asymmetry — with five further tests proving the diff would catch a stray statement, a duplicated member, or a statement merely moved within one arm. If you edit one arm, edit the other in the same change.

Scope

Good for: transaction simulation and preview, gas analysis, debug_traceTransaction-style tracing without a tracing node, contract-bytecode analysis and disassembly, replaying historical transactions against archive state, source-level debugging, block replay and consensus testing, and — via Nethereum.EVM.Core — stateless/zkVM execution.

Not: a P2P client. It executes; it does not gossip, mine, or maintain a chain. For a full node see Nethereum.CoreChain and Nethereum.DevP2P.

Precompile coverage depends on what you wire: DefaultPrecompileRegistries.FrontierBase carries 0x01–0x04, every base registry from ByzantiumBase on carries real handlers for 0x01–0x09, and OsakaBase adds 0x100 (P256VERIFY). The KZG and BLS addresses are registered but unwired — 0x0a from CancunBase, 0x0b–0x11 from PragueBase — as a PlaceholderPrecompile that throws UnwiredPrecompileException rather than returning a wrong result; supply them with WithKzgBackend / WithBlsBackend from the companion packages.

See also

  • Nethereum.EVM.Core — the shared engine and the guest build.
  • Nethereum.EVM.Precompiles — the default crypto backends, source-linked into this package.
  • Nethereum.EVM.Precompiles.Bls / Nethereum.EVM.Precompiles.Kzg — BLS12-381 and KZG.
  • Nethereum.CoreChain — block production, chain storage, and the Patricia root calculators.
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net451 is compatible.  net452 was computed.  net46 was computed.  net461 is compatible.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (10)

Showing the top 5 NuGet packages that depend on Nethereum.EVM:

Package Downloads
Nethereum.BlockchainProcessing

Nethereum.BlockchainProcessing Ethereum blockchain processing allowing to crawl Blocks, Transactions, TransactionReceipts and Logs (Event) for storage and / or using custom handlers like queuing , search, etc

Nethereum.EVM.Contracts

Nethereum.EVM.Contracts EVM Simulation of Standard Contracts and Common contracts

Nethereum.DataServices

Nethereum DataServices library, provides client access to different external services like the Etherscan rest apis

Nethereum.CoreChain

Nethereum CoreChain - Core blockchain infrastructure for state, transactions, and receipts root management

Nethereum.ChainStateVerification

Verified execution-state primitives (account/storage/receipt proofs rooted in the light client).

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
7.0.0 1,455 10/2/2026
6.1.0 3,573 3/25/2026
6.0.4 728 3/18/2026
6.0.3 567 3/18/2026
6.0.1 628 3/17/2026
6.0.0 597 3/16/2026
5.8.0 392 1/6/2026
5.0.0 510 5/28/2025
4.29.0 482 2/10/2025
4.28.0 402 1/7/2025
4.27.1 409 12/24/2024
4.27.0 384 12/24/2024
4.26.0 481 10/1/2024
4.25.0 433 9/19/2024
4.21.4 494 8/9/2024
4.21.3 420 7/22/2024
4.21.2 498 6/26/2024
4.21.1 438 6/26/2024
4.21.0 484 6/18/2024
4.20.0 877 3/28/2024
Loading failed