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
paket add Nethereum.EVM --version 7.0.0
#r "nuget: Nethereum.EVM, 7.0.0"
#:package Nethereum.EVM@7.0.0
#addin nuget:?package=Nethereum.EVM&version=7.0.0
#tool nuget:?package=Nethereum.EVM&version=7.0.0
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_getTransactionCountandeth_getBlockByNumber, pluseth_getProofanddebug_storageRangeAton the two paths listed underRpcNodeDataServicebelow. - 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 and ContainsFunctionSignatures(List — 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 |
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, 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 |
engine → RPC |
EvmLog.ToFilterLog() / List |
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 | Versions 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. |
-
.NETFramework 4.5.1
- Nethereum.ABI (>= 7.0.0)
- Nethereum.Contracts (>= 7.0.0)
- Nethereum.Hex (>= 7.0.0)
- Nethereum.RPC (>= 7.0.0)
- Nethereum.Signer (>= 7.0.0)
- Newtonsoft.Json (>= 11.0.2 && < 14.0.0)
-
.NETFramework 4.6.1
- Nethereum.ABI (>= 7.0.0)
- Nethereum.Contracts (>= 7.0.0)
- Nethereum.Hex (>= 7.0.0)
- Nethereum.RPC (>= 7.0.0)
- Nethereum.Signer (>= 7.0.0)
- Newtonsoft.Json (>= 11.0.2 && < 14.0.0)
-
.NETStandard 2.0
- Nethereum.ABI (>= 7.0.0)
- Nethereum.Contracts (>= 7.0.0)
- Nethereum.Hex (>= 7.0.0)
- Nethereum.RPC (>= 7.0.0)
- Nethereum.Signer (>= 7.0.0)
- NETStandard.Library (>= 2.0.3)
- Newtonsoft.Json (>= 11.0.2 && < 14.0.0)
-
net10.0
- Nethereum.ABI (>= 7.0.0)
- Nethereum.Contracts (>= 7.0.0)
- Nethereum.Hex (>= 7.0.0)
- Nethereum.RPC (>= 7.0.0)
- Nethereum.Signer (>= 7.0.0)
- Newtonsoft.Json (>= 11.0.2 && < 14.0.0)
-
net6.0
- Nethereum.ABI (>= 7.0.0)
- Nethereum.Contracts (>= 7.0.0)
- Nethereum.Hex (>= 7.0.0)
- Nethereum.RPC (>= 7.0.0)
- Nethereum.Signer (>= 7.0.0)
- Newtonsoft.Json (>= 11.0.2 && < 14.0.0)
-
net8.0
- Nethereum.ABI (>= 7.0.0)
- Nethereum.Contracts (>= 7.0.0)
- Nethereum.Hex (>= 7.0.0)
- Nethereum.RPC (>= 7.0.0)
- Nethereum.Signer (>= 7.0.0)
- Newtonsoft.Json (>= 11.0.2 && < 14.0.0)
-
net9.0
- Nethereum.ABI (>= 7.0.0)
- Nethereum.Contracts (>= 7.0.0)
- Nethereum.Hex (>= 7.0.0)
- Nethereum.RPC (>= 7.0.0)
- Nethereum.Signer (>= 7.0.0)
- Newtonsoft.Json (>= 11.0.2 && < 14.0.0)
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 |