![]() |
VOOZH | about |
dotnet add package Nethereum.EVM --version 6.1.0
NuGet\Install-Package Nethereum.EVM -Version 6.1.0
<PackageReference Include="Nethereum.EVM" Version="6.1.0" />
<PackageVersion Include="Nethereum.EVM" Version="6.1.0" />Directory.Packages.props
<PackageReference Include="Nethereum.EVM" />Project file
paket add Nethereum.EVM --version 6.1.0
#r "nuget: Nethereum.EVM, 6.1.0"
#:package Nethereum.EVM@6.1.0
#addin nuget:?package=Nethereum.EVM&version=6.1.0Install as a Cake Addin
#tool nuget:?package=Nethereum.EVM&version=6.1.0Install as a Cake Tool
Nethereum.EVM is a production-ready Ethereum Virtual Machine (EVM) execution engine that runs bytecode instruction-by-instruction with full trace support and gas calculation. Passes all Ethereum VM and State tests.
This package provides a local EVM implementation that can:
Status: Production - passes all Ethereum VM and State tests. Purpose-built for development tooling, testing, debugging, and simulation.
dotnet add package Nethereum.EVM
Main execution engine that processes EVM bytecode. Located in EVMSimulator.cs:30-417.
Key Methods:
ExecuteAsync(Program program, ...) - Executes program until completionStepAsync(Program program, ...) - Executes single instructionExample: Basic Bytecode Execution
using Nethereum.EVM;
using Nethereum.Hex.HexConvertors.Extensions;
// Execute PUSH1 0xA0 (pushes 0xA0 onto stack)
var vm = new EVMSimulator();
var program = new Program("60A0".HexToByteArray());
await vm.StepAsync(program, 0);
var result = program.StackPeek(); // Returns: 0x00000000...0000A0
Example: Multi-Instruction Execution
// Execute: PUSH1 0x04, PUSH1 0x04, ADD (4 + 4 = 8)
var vm = new EVMSimulator();
var bytecode = "6004600401".HexToByteArray(); // PUSH1 0x04, PUSH1 0x04, ADD
var program = new Program(bytecode);
// Step through each instruction
await vm.StepAsync(program, 0); // PUSH1 0x04
await vm.StepAsync(program, 1); // PUSH1 0x04
await vm.StepAsync(program, 2); // ADD
var result = program.StackPeek(); // Returns: 0x0000...0008
From test: EvmSimulatorTests.cs:232-237
Represents EVM program state including stack, memory, storage, and instructions. Located in Program.cs:14-293.
Key Properties:
Instructions - Parsed bytecode instructionsMemory - EVM memory (expandable byte array)Trace - Execution trace historyProgramResult - Execution outcomeProgramContext - Execution context (addresses, block data, gas)MAX_STACKSIZE = 1024 - Stack limitStack Operations (Program.cs:102-170):
// Stack operations (32-byte values, stack grows downward)
program.StackPush(value); // Push 32-byte value
var top = program.StackPeek(); // Peek at top
var item = program.StackPeekAt(2); // Peek at position 2
program.StackPop(); // Remove top
program.StackSwap(1); // Swap positions
Memory Operations (Program.cs:171-209):
// Memory expands automatically (32-byte increments)
program.WriteToMemory(index, totalSize, data, extend: true);
// Memory is accessed directly via the Memory property (List<byte>)
var memorySize = program.Memory.Count;
Execution environment with blockchain state. Located in ProgramContext.cs:16-45.
Properties:
AddressContract - Contract being executedAddressCaller - Caller addressAddressOrigin - Transaction originatorGas, Value, ChainId - Transaction parametersBlockNumber, Timestamp, Coinbase, BaseFee, BlobBaseFee - Block contextGasPrice, GasLimit, Difficulty - Network parametersTransientStorage - EIP-1153 transient storageExecutionStateService - State managementExample: Creating Context
var callInput = new CallInput
{
From = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
To = "0x1234567890123456789012345678901234567890",
Value = new HexBigInteger(1000000000000000000), // 1 ETH
Gas = new HexBigInteger(21000),
ChainId = 1
};
var executionStateService = new ExecutionStateService(nodeDataService);
var context = new ProgramContext(callInput, executionStateService);
Complete EVM opcode set. Located in Instruction.cs:4-329.
Categories:
Arithmetic & Logic (0x00-0x1F):
ADD, MUL, SUB, DIV, SDIV, MOD, SMOD, ADDMOD, MULMOD, EXP, SIGNEXTENDLT, GT, SLT, SGT, EQ, ISZERO, AND, OR, XOR, NOT, BYTE, SHL, SHR, SARCryptographic (0x20):
KECCAK256 - SHA3-256 hashEnvironment & Context (0x30-0x4A):
ADDRESS, BALANCE, ORIGIN, CALLER, CALLVALUE, CALLDATALOAD, CALLDATASIZE, CALLDATACOPYCODESIZE, CODECOPY, GASPRICE, EXTCODESIZE, EXTCODECOPY, RETURNDATASIZE, RETURNDATACOPY, EXTCODEHASHBLOCKHASH, COINBASE, TIMESTAMP, NUMBER, DIFFICULTY, GASLIMIT, CHAINID, SELFBALANCE, BASEFEEBLOBHASH, BLOBBASEFEE (Cancun fork)Stack, Memory, Storage (0x50-0x5F):
POP, MLOAD, MSTORE, MSTORE8, SLOAD, SSTORE, JUMP, JUMPI, PC, MSIZE, GAS, JUMPDESTTLOAD, TSTORE (Cancun - EIP-1153 transient storage)MCOPY (Cancun - memory copy)PUSH0 (Shanghai - EIP-3855)Push Operations (0x60-0x7F):
PUSH1 through PUSH32 - Push 1-32 bytes onto stackDuplicate Operations (0x80-0x8F):
DUP1 through DUP16 - Duplicate stack itemsSwap Operations (0x90-0x9F):
SWAP1 through SWAP16 - Swap stack itemsLogging (0xA0-0xA4):
LOG0, LOG1, LOG2, LOG3, LOG4 - Event loggingContract Operations (0xF0-0xFF):
CREATE, CALL, CALLCODE, RETURN, DELEGATECALL, CREATE2, STATICCALL, REVERT, INVALID, SELFDESTRUCTComprehensive gas calculation for all opcodes with EIP-2929 (warm/cold access) support. Located in Gas/OpcodeGasTable.cs:10-523.
Static Gas Costs (OpcodeGasTable.cs:12-109):
// Common static costs
ADD = 3
MUL = 5
PUSH1-PUSH32 = 3
DUP1-DUP16 = 3
SWAP1-SWAP16 = 3
SELFDESTRUCT = -1 (dynamic)
TLOAD, TSTORE = 100 (transient storage)
Dynamic Gas Calculation:
Operations marked with -1 cost have dynamic gas calculated based on:
Example: SSTORE Gas (OpcodeGasTable.cs:280-318)
// SSTORE has complex gas calculation:
// - Cold access: +2100 gas
// - Setting from zero: 20000 gas
// - Setting from non-zero to different non-zero: 2900 gas (if original value)
// - Setting to same value: 100 gas
// - Dirty slot (already modified): 100 gas
Example: CALL Gas (OpcodeGasTable.cs:357-396)
// CALL gas includes:
// - Cold account access: 2600 gas (warm: 100 gas)
// - Memory expansion cost
// - Value transfer: +9000 gas
// - Account creation (if empty): +25000 gas
Manages account states during execution. Located in BlockchainState/ExecutionStateService.cs:11-137.
Key Methods:
GetFromStorageAsync(address, key) - Fetch storage with cachingGetCodeAsync(address) - Fetch contract codeGetNonceAsync(address) - Fetch account nonceGetTotalBalanceAsync(address) - Fetch account balanceSaveToStorage(address, key, value) - Update storageLoadBalanceNonceAndCodeFromStorageAsync(address) - Load full account stateMarkAddressAsWarm(address) - Track warm addresses for gas calculationExample: Using with RPC Node
using Nethereum.EVM.BlockchainState;
using Nethereum.Web3;
var web3 = new Web3("https://mainnet.infura.io/v3/YOUR_KEY");
var nodeDataService = new RpcNodeDataService(
web3.Eth,
BlockParameter.CreateLatest()
);
var stateService = new ExecutionStateService(nodeDataService);
// Fetch and cache account data
var code = await stateService.GetCodeAsync("0x1234...");
var storage = await stateService.GetFromStorageAsync("0x1234...", BigInteger.Zero);
var balance = await stateService.GetTotalBalanceAsync("0x1234...");
From: RpcNodeDataService.cs:14-109
Execution outcome with results, logs, and tracking. Located in ProgramResult.cs:11-42.
Properties:
Result - Return data (byte[])Logs - Event logs (FilterLog list)IsRevert - Revert flagIsSelfDestruct - Self-destruct flagDeletedContractAccounts - Destroyed contractsCreatedContractAccounts - Created contractsInnerCalls - Sub-calls madeInnerContractCodeCalls - Called contract codesException - Execution exceptionExample: Handling Results
await vm.ExecuteAsync(program);
var result = program.ProgramResult;
if (result.IsRevert)
{
// Decode revert message (ABI-encoded Error(string))
var message = result.GetRevertMessage();
Console.WriteLine($"Reverted: {message}");
}
else
{
var returnData = result.Result;
foreach (var log in result.Logs)
{
Console.WriteLine($"Log: {log.Topics[0]}");
}
}
Execution trace for debugging. Located in ProgramTrace.cs:9-101.
Properties:
ProgramAddress, CodeAddress - Execution addressesVMTraceStep, ProgramTraceStep - Step countersDepth - Call depthInstruction - Executed instructionStack - Stack state snapshotMemory - Memory state snapshotStorage - Storage state snapshotGasCost - Gas consumed by instructionExample: Analyzing Traces
var vm = new EVMSimulator();
var program = new Program(bytecode, context);
await vm.ExecuteAsync(program, traceEnabled: true);
foreach (var trace in program.Trace)
{
Console.WriteLine(trace.ToString());
// Output includes:
// - Address, VMTraceStep, Depth, Gas
// - Instruction with arguments
// - Stack contents
// - Memory contents
// - Storage changes
}
From: ProgramTrace.cs:79-99
Parse and disassemble EVM bytecode. Located in ProgramInstructionsUtils.cs:8-146.
Disassembly Methods:
using Nethereum.EVM;
var bytecode = "0x60806040526004361060...";
// Parse into instructions
var instructions = ProgramInstructionsUtils.GetProgramInstructions(bytecode);
// Full disassembly
var disassembly = ProgramInstructionsUtils.DisassembleToString(bytecode);
// Output format: "0000 60 PUSH1 0x80"
// Simplified disassembly
var simplified = ProgramInstructionsUtils.DisassembleSimplifiedToString(bytecode);
// Output format: "PUSH1 0x80 PUSH1 0x40 MSTORE"
Function Signature Detection:
var instructions = ProgramInstructionsUtils.GetProgramInstructions(contractCode);
// Check for specific function signature
bool hasTransfer = ProgramInstructionsUtils.ContainsFunctionSignature(
instructions,
"0xa9059cbb" // transfer(address,uint256)
);
// Check for multiple signatures
var signatures = new[] { "0xa9059cbb", "0x70a08231" }; // transfer, balanceOf
bool hasAll = ProgramInstructionsUtils.ContainsFunctionSignatures(instructions, signatures);
From: ProgramInstructionsUtils.cs:10-35, 38-146
using Nethereum.EVM;
using Nethereum.Hex.HexConvertors.Extensions;
using System.Numerics;
// Test: 2 + 2 with ADDMOD 3 (should equal 1)
var bytecode = "60036002600208"; // PUSH1 0x03, PUSH1 0x02, PUSH1 0x02, ADDMOD
var vm = new EVMSimulator();
var program = new Program(bytecode.HexToByteArray());
// Execute all instructions
await vm.ExecuteAsync(program, traceEnabled: false);
var result = program.StackPeek();
// result = 0x0000...0001 (4 % 3 = 1)
From test pattern: EvmSimulatorTests.cs:253-258
using Nethereum.EVM;
using Nethereum.Hex.HexConvertors.Extensions;
// Store 0x01 at memory[0], then hash 1 byte starting at memory[0]
// PUSH1 0x01, PUSH1 0x00, MSTORE8, PUSH1 0x01, PUSH1 0x00, KECCAK256
var bytecode = "6001600053600160002016".HexToByteArray();
var vm = new EVMSimulator();
var program = new Program(bytecode);
await vm.ExecuteAsync(program);
var hash = program.StackPeek().ToHex();
// hash = keccak256(0x01) = "5fe7f977e71dba2ea1a68e21057beebb9be2ac30c6410aa38d4f3fbe41dcffd2"
From test: EvmSimulatorTests.cs:294-297
using Nethereum.EVM;
using Nethereum.Hex.HexConvertors.Extensions;
// PUSH1 0x01 (condition=true), PUSH1 0x05 (jump target), JUMPI, JUMPDEST, PUSH1 0xCC
// If condition is true, jump to JUMPDEST at position 5, then push 0xCC
var bytecode = "60016005575B60CC".HexToByteArray();
var vm = new EVMSimulator();
var program = new Program(bytecode);
await vm.ExecuteAsync(program);
var result = program.StackPeek();
// result = 0xCC (jump was taken)
From test: EvmSimulatorTests.cs:306-309
using Nethereum.EVM;
using Nethereum.EVM.BlockchainState;
using Nethereum.Web3;
using Nethereum.RPC.Eth.DTOs;
using Nethereum.Hex.HexTypes;
// Connect to Ethereum node
var web3 = new Web3("https://mainnet.infura.io/v3/YOUR_KEY");
// Create node data service for fetching blockchain state
var nodeDataService = new RpcNodeDataService(
web3.Eth,
BlockParameter.CreateLatest()
);
var stateService = new ExecutionStateService(nodeDataService);
// Create call input
var callInput = new CallInput
{
From = "0x0000000000000000000000000000000000000001",
To = "0xContractAddress",
Gas = new HexBigInteger(1000000),
ChainId = 1,
Data = "0x" // Function call data
};
// Create program context
var context = new ProgramContext(callInput, stateService);
// Get contract bytecode from blockchain
var contractCode = await stateService.GetCodeAsync(callInput.To);
// Execute contract code
var vm = new EVMSimulator();
var program = new Program(contractCode, context);
await vm.ExecuteAsync(program, traceEnabled: true);
// Check results
if (program.ProgramResult.IsRevert)
{
Console.WriteLine($"Reverted: {program.ProgramResult.GetRevertMessage()}");
}
else
{
var returnData = program.ProgramResult.Result;
Console.WriteLine($"Success: {returnData.ToHex()}");
}
// Analyze gas usage
var totalGas = program.Trace.Sum(t => t.GasCost);
Console.WriteLine($"Gas used: {totalGas}");
using Nethereum.EVM;
using Nethereum.Web3;
var web3 = new Web3("https://mainnet.infura.io/v3/YOUR_KEY");
// Fetch USDC contract bytecode
var usdcAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
var bytecode = await web3.Eth.GetCode.SendRequestAsync(usdcAddress);
// Parse into instructions
var instructions = ProgramInstructionsUtils.GetProgramInstructions(bytecode);
Console.WriteLine($"Total instructions: {instructions.Count}");
// Check for specific functions
bool hasTransfer = ProgramInstructionsUtils.ContainsFunctionSignature(
instructions,
"0xa9059cbb" // transfer(address,uint256) signature
);
Console.WriteLine($"Has transfer function: {hasTransfer}");
// Full disassembly
var disassembly = ProgramInstructionsUtils.DisassembleToString(bytecode);
Console.WriteLine(disassembly);
// Output format:
// 0000 60 PUSH1 0x80
// 0002 60 PUSH1 0x40
// 0004 52 MSTORE
// ...
using Nethereum.EVM;
using Nethereum.Hex.HexConvertors.Extensions;
// Bytecode: PUSH1 0xF0, PUSH1 0x0F, OR (0xF0 | 0x0F = 0xFF)
var bytecode = "60F0600F17".HexToByteArray();
var vm = new EVMSimulator();
var program = new Program(bytecode);
// Step through each instruction
int step = 0;
// Step 1: PUSH1 0xF0
await vm.StepAsync(program, step++);
Console.WriteLine($"After PUSH1 0xF0: Stack top = {program.StackPeek().ToHex()}");
// Stack: [0xF0]
// Step 2: PUSH1 0x0F
await vm.StepAsync(program, step++);
Console.WriteLine($"After PUSH1 0x0F: Stack has {program.GetCurrentStackAsHex().Count} items");
// Stack: [0x0F, 0xF0]
// Step 3: OR
await vm.StepAsync(program, step++);
Console.WriteLine($"After OR: Stack top = {program.StackPeek().ToHex()}");
// Stack: [0xFF]
// Check traces
foreach (var trace in program.Trace)
{
Console.WriteLine($"Step {trace.VMTraceStep}: {trace.Instruction?.Instruction} - Gas: {trace.GasCost}");
}
From test pattern: EvmSimulatorTests.cs:117-121, 334-344
First access (cold) costs more:
Subsequent access (warm) costs less:
Located in OpcodeGasTable.cs:224-278, 478-486
Complex gas calculation based on EIP-2200 and EIP-2929:
// Cold storage access: +2100 gas (first time)
// Warm storage access: 0 gas (already accessed)
// Value changes:
// 1. Setting from zero to non-zero: 20000 gas (new slot)
// 2. Setting from non-zero to different non-zero: 2900 gas (if original value)
// 3. Setting to same value: 100 gas (no-op)
// 4. Dirty slot (already modified in transaction): 100 gas
Located in OpcodeGasTable.cs:280-318
Memory expands in 32-byte increments with quadratic cost:
// Gas cost = memory_size_word * 3 + floor(memory_size_word^2 / 512)
// where memory_size_word = ceil(memory_size_byte / 32)
Located in Program.cs:256-266
CALL, CALLCODE, DELEGATECALL, STATICCALL have multiple cost components:
// Base costs:
// - Account access (warm: 100, cold: 2600)
// - Memory expansion (input + output)
// - Value transfer: +9000 gas (if value > 0)
// - Account creation: +25000 gas (if target is empty account and value > 0)
Located in OpcodeGasTable.cs:357-462
Cancun (Latest):
BLOBHASH (0x49) - Get blob versioned hashesBLOBBASEFEE (0x4A) - Blob base feeTLOAD (0x5C) - Transient storage load (EIP-1153)TSTORE (0x5D) - Transient storage store (EIP-1153)MCOPY (0x5E) - Memory copyShanghai:
PUSH0 (0x5F) - Push zero onto stack (EIP-3855)London:
BASEFEE (0x48) - Current block's base fee (EIP-3198)Istanbul:
CHAINID (0x46) - Network chain ID (EIP-1344)SELFBALANCE (0x47) - Contract's own balance (EIP-1884)Constantinople:
CREATE2 (0xF5) - Deterministic contract creation (EIP-1014)EXTCODEHASH (0x3F) - Contract code hash (EIP-1052)SHL, SHR, SAR (0x1B-0x1D) - Bit shiftingByzantium:
RETURNDATASIZE (0x3D), RETURNDATACOPY (0x3E) - Return data (EIP-211)STATICCALL (0xFA) - Static call (EIP-214)REVERT (0xFD) - Revert with data (EIP-140)From: Instruction.cs:4-329
This EVM implementation is designed for:
Not designed for:
BLOCKHASH limited by RPC node capabilitiesImplement INodeDataService for custom state sources:
public class CustomStateProvider : INodeDataService
{
public async Task<BigInteger> GetBalanceAsync(string address)
{
// Custom balance logic
return BigInteger.Zero;
}
public async Task<byte[]> GetCodeAsync(string address)
{
// Custom code retrieval
return new byte[0];
}
public async Task<byte[]> GetStorageAtAsync(string address, BigInteger position)
{
// Custom storage logic
return new byte[32];
}
// Implement other INodeDataService methods...
}
From interface: INodeDataService.cs:6-18
Use debug_storageRangeAt for precise storage state at transaction index:
var nodeDataService = new RpcNodeDataService(
web3.Eth,
BlockParameter.CreateLatest(),
web3.DebugApiService,
blockHash: "0xabc123...",
transactionIndex: 5,
useDebugStorageAt: true
);
// Storage reads will use debug_storageRangeAt for transaction-level precision
From: RpcNodeDataService.cs:23-90
The TransactionExecutor provides a complete EVM transaction execution pipeline that handles gas calculation, EIP-1559 fee logic, EIP-4844 blob transactions, EIP-7702 authorization lists, and state management.
public class TransactionExecutor
{
public TransactionExecutor(
HardforkConfig config = null,
EVMSimulator evmSimulator = null,
IPrecompileProvider customPrecompileProvider = null);
public async Task<TransactionExecutionResult> ExecuteAsync(TransactionExecutionContext ctx);
}
The constructor accepts an optional HardforkConfig (defaults to HardforkConfig.Default), an optional pre-configured EVMSimulator, and an optional custom IPrecompileProvider that gets composed with the config's built-in provider via CompositePrecompileProvider.
From: TransactionExecutor.cs:17-81
public enum ExecutionMode
{
Transaction, // Full transaction execution with gas payment
Call // eth_call mode - skips gas price validation
}
From: TransactionExecutionContext.cs:9-13
All inputs needed to execute a transaction:
Transaction fields:
| Property | Type | Description |
|---|---|---|
Mode |
ExecutionMode |
Transaction or Call mode |
IsCallMode |
bool |
Shorthand for Mode == ExecutionMode.Call |
Sender |
string |
Transaction sender address |
To |
string |
Destination address (null for contract creation) |
Data |
byte[] |
Transaction calldata |
GasLimit |
BigInteger |
Gas limit |
Value |
BigInteger |
ETH value in wei |
GasPrice |
BigInteger |
Legacy gas price |
MaxFeePerGas |
BigInteger |
EIP-1559 max fee |
MaxPriorityFeePerGas |
BigInteger |
EIP-1559 priority fee |
EffectiveGasPrice |
BigInteger |
Calculated effective gas price |
Nonce |
BigInteger |
Sender nonce |
IsEip1559 |
bool |
Whether this is a type-2 transaction |
IsContractCreation |
bool |
Whether this creates a contract |
IsType3Transaction |
bool |
EIP-4844 blob transaction |
IsType4Transaction |
bool |
EIP-7702 authorization transaction |
BlobVersionedHashes |
List<string> |
Blob versioned hashes (type-3) |
MaxFeePerBlobGas |
BigInteger |
Max blob gas fee (type-3) |
AccessList |
List<AccessListEntry> |
EIP-2930 access list |
AuthorisationList |
List<Authorisation7702Signed> |
EIP-7702 authorizations |
Block context:
| Property | Type | Description |
|---|---|---|
BlockNumber |
long |
Current block number |
Timestamp |
long |
Block timestamp |
Coinbase |
string |
Block coinbase address |
BaseFee |
BigInteger |
Block base fee |
Difficulty |
BigInteger |
Block difficulty |
BlockGasLimit |
BigInteger |
Block gas limit |
ExcessBlobGas |
BigInteger |
Excess blob gas |
BlobBaseFee |
BigInteger |
Blob base fee |
ChainId |
BigInteger |
Chain ID |
Computed/state fields:
| Property | Type | Description |
|---|---|---|
IntrinsicGas |
BigInteger |
Calculated intrinsic gas cost |
FloorGas |
BigInteger |
EIP-7623 floor gas limit |
MinGasRequired |
BigInteger |
Max of intrinsic and floor gas |
BlobGasCost |
BigInteger |
Total blob gas cost |
ContractAddress |
string |
Created contract address |
ExecutionState |
ExecutionStateService |
State service for account/storage access |
SenderAccount |
AccountExecutionState |
Sender's account state |
Code |
byte[] |
Bytecode to execute |
DelegateAddress |
string |
EIP-7702 delegate address |
TraceEnabled |
bool |
Whether to capture execution traces |
From: TransactionExecutionContext.cs:15-65
public class TransactionExecutionResult
{
public bool Success { get; set; }
public BigInteger GasUsed { get; set; }
public BigInteger GasRefund { get; set; }
public BigInteger EffectiveGasUsed { get; set; }
public byte[] ReturnData { get; set; }
public string RevertReason { get; set; }
public List<FilterLog> Logs { get; set; }
public byte[] StateRoot { get; set; }
public string ContractAddress { get; set; }
public string Error { get; set; }
public bool IsValidationError { get; set; }
public List<ProgramTrace> Traces { get; set; }
public List<string> CreatedAccounts { get; set; }
public List<string> DeletedAccounts { get; set; }
public List<CallInput> InnerCalls { get; set; }
public Dictionary<string, List<ProgramInstruction>> InnerContractCodeCalls { get; set; }
public ProgramResult ProgramResult { get; set; }
}
From: TransactionExecutionResult.cs:7-26
public enum TransactionError
{
None,
InsufficientMaxFeePerGas,
PriorityGreaterThanMaxFee,
InsufficientBalance,
GasAllowanceExceeded,
IntrinsicGasTooLow,
NonceIsMax,
SenderNotEOA,
InitcodeSizeExceeded,
Type3TxContractCreation,
Type3TxZeroBlobs,
Type3TxBlobCountExceeded,
Type3TxInvalidBlobVersionedHash,
AddressCollision,
InvalidEFPrefix,
MaxCodeSizeExceeded,
OutOfGas,
Reverted,
}
From: TransactionExecutionResult.cs:28-48
public class HardforkConfig
{
public bool EnableEIP4844 { get; set; } // Blob transactions (Cancun)
public bool EnableEIP7623 { get; set; } // Calldata gas floor (Prague)
public bool EnableEIP7702 { get; set; } // Authorization lists (Prague)
public int MaxBlobsPerBlock { get; set; }
public IPrecompileProvider PrecompileProvider { get; set; }
public static HardforkConfig Cancun { get; } // EIP-4844 only, 6 blobs, Cancun precompiles (1-10)
public static HardforkConfig Prague { get; } // All EIPs, 9 blobs, Prague precompiles (1-17)
public static HardforkConfig Default { get; } // Same as Prague
public static HardforkConfig FromName(string hardfork); // "cancun" or "prague"
}
From: HardforkConfig.cs:1-48
This example shows the wallet preview pattern used by StateChangesPreviewService to simulate a transaction and extract state changes:
var nodeDataService = new RpcNodeDataService(web3.Eth, new BlockParameter(blockNumber));
var executionStateService = new ExecutionStateService(nodeDataService);
var ctx = new TransactionExecutionContext
{
Mode = ExecutionMode.Call,
Sender = callInput.From,
To = callInput.To,
Data = callInput.Data?.HexToByteArray(),
GasLimit = 10_000_000,
Value = callInput.Value?.Value ?? BigInteger.Zero,
GasPrice = baseFee + 1_000_000_000,
MaxFeePerGas = callInput.MaxFeePerGas?.Value ?? baseFee + 1_000_000_000,
MaxPriorityFeePerGas = callInput.MaxPriorityFeePerGas?.Value ?? 1_000_000_000,
Nonce = BigInteger.Zero,
IsEip1559 = callInput.MaxFeePerGas != null,
IsContractCreation = string.IsNullOrEmpty(callInput.To),
BlockNumber = (long)blockNumber.Value,
Timestamp = timestamp,
BaseFee = baseFee,
Coinbase = "0x0000000000000000000000000000000000000000",
BlockGasLimit = 30_000_000,
ExecutionState = executionStateService,
TraceEnabled = true
};
var config = HardforkConfig.Default;
var executor = new TransactionExecutor(config);
var execResult = await executor.ExecuteAsync(ctx);
if (execResult.Success)
{
// Decode and extract state changes
var decoder = new ProgramResultDecoder(abiStorage);
var decodedResult = decoder.Decode(execResult, callInput, chainId);
var extractor = new StateChangesExtractor();
var stateChanges = extractor.ExtractFromDecodedResult(
decodedResult, executionStateService, currentUserAddress);
}
Reference: StateChangesPreviewService.cs in Nethereum.Wallet
The decoding pipeline transforms raw EVM execution results into human-readable decoded calls, logs, errors, and return values using ABI information from IABIInfoStorage.
public class ProgramResultDecoder
{
public ProgramResultDecoder(IABIInfoStorage abiStorage);
public DecodedProgramResult Decode(
Program program, CallInput initialCall, BigInteger chainId);
public DecodedProgramResult Decode(
TransactionExecutionResult executionResult, CallInput initialCall, BigInteger chainId);
public DecodedProgramResult Decode(
ProgramResult programResult, List<ProgramTrace> trace,
CallInput initialCall, BigInteger chainId);
public DecodedCall DecodeCall(CallInput call, BigInteger chainId, int depth);
public DecodedLog DecodeLog(FilterLog log, BigInteger chainId);
public DecodedError DecodeRevert(byte[] revertData, BigInteger chainId, string contractAddress);
public List<ParameterOutput> DecodeReturnValue(FunctionABI functionABI, string output);
}
The decoder resolves function signatures, event topics, and error selectors against IABIInfoStorage. It supports three input types: a Program (from EVMSimulator), a TransactionExecutionResult (from TransactionExecutor), or a raw ProgramResult with trace.
From: Decoding/ProgramResultDecoder.cs:13-282
public class DecodedProgramResult
{
public DecodedCall RootCall { get; set; }
public List<DecodedLog> DecodedLogs { get; set; }
public List<ParameterOutput> ReturnValue { get; set; }
public DecodedError RevertReason { get; set; }
public bool IsRevert { get; set; }
public bool IsSuccess { get; } // !IsRevert
public ProgramResult OriginalResult { get; set; }
public CallInput OriginalCall { get; set; }
public BigInteger ChainId { get; set; }
public string ToHumanReadableString(); // Formatted call tree, events, and result
}
From: Decoding/DecodedProgramResult.cs:9-19
public enum CallType
{
Call, DelegateCall, StaticCall, CallCode, Create, Create2
}
public class DecodedCall
{
public string From { get; set; }
public string To { get; set; }
public string ContractName { get; set; }
public FunctionABI Function { get; set; }
public List<ParameterOutput> InputParameters { get; set; }
public List<ParameterOutput> OutputParameters { get; set; }
public List<DecodedCall> InnerCalls { get; set; }
public List<DecodedLog> Logs { get; set; }
public CallType CallType { get; set; }
public int Depth { get; set; }
public bool IsDecoded { get; set; }
public string RawInput { get; set; }
public string RawOutput { get; set; }
public BigInteger Value { get; set; }
public BigInteger GasUsed { get; set; }
public bool IsRevert { get; set; }
public DecodedError Error { get; set; }
public CallInput OriginalCall { get; set; }
public string GetFunctionSignature(); // Returns Function.Sha3Signature
public string GetFunctionName(); // Returns Function.Name
public string GetDisplayName(); // "ContractName.FunctionName" or fallback
}
From: Decoding/DecodedCall.cs:9-68
public class DecodedLog
{
public string ContractAddress { get; set; }
public string ContractName { get; set; }
public EventABI Event { get; set; }
public List<ParameterOutput> Parameters { get; set; }
public bool IsDecoded { get; set; }
public FilterLog OriginalLog { get; set; }
public int LogIndex { get; set; }
public int CallDepth { get; set; }
public string GetEventSignature(); // Returns Event.Sha3Signature
public string GetEventName(); // Returns Event.Name
public string GetDisplayName(); // "ContractName.EventName" or fallback
}
From: Decoding/DecodedLog.cs:8-43
public class DecodedError
{
public ErrorABI Error { get; set; }
public List<ParameterOutput> Parameters { get; set; }
public string Message { get; set; }
public bool IsStandardError { get; set; }
public bool IsDecoded { get; set; }
public string RawData { get; set; }
public string GetErrorSignature(); // Returns Error.Sha3Signature
public string GetErrorName(); // Returns Error.Name
public string GetDisplayMessage(); // Message, Error.Name, or RawData fallback
public static DecodedError FromStandardError(string message, string rawData = null);
public static DecodedError FromUnknownError(string rawData);
}
From: Decoding/DecodedError.cs:7-61
// After executing a transaction
var executor = new TransactionExecutor(HardforkConfig.Default);
var execResult = await executor.ExecuteAsync(ctx);
// Decode the result using ABI storage
var decoder = new ProgramResultDecoder(abiStorage);
var decoded = decoder.Decode(execResult, callInput, chainId);
// Inspect the decoded result
Console.WriteLine(decoded.ToHumanReadableString());
// Access individual parts
if (decoded.IsSuccess)
{
foreach (var param in decoded.ReturnValue)
{
Console.WriteLine($"{param.Parameter.Name}: {param.Result}");
}
}
else
{
Console.WriteLine($"Reverted: {decoded.RevertReason?.GetDisplayMessage()}");
}
// Walk the call tree
Console.WriteLine($"Root: {decoded.RootCall.GetDisplayName()}");
foreach (var inner in decoded.RootCall.InnerCalls)
{
Console.WriteLine($" -> {inner.GetDisplayName()}");
}
// Inspect events
foreach (var log in decoded.DecodedLogs)
{
Console.WriteLine($"Event: {log.GetDisplayName()}");
}
The state changes extraction pipeline analyzes decoded execution results to identify all balance changes (native ETH, ERC20, ERC721, ERC1155) that occurred during a transaction.
public class StateChangesExtractor : IStateChangesExtractor
{
public StateChangesResult ExtractFromDecodedResult(
DecodedProgramResult decodedResult,
ExecutionStateService stateService = null,
string currentUserAddress = null);
public StateChangesResult ExtractFromDecodedResult(
DecodedProgramResult decodedResult,
ExecutionStateService stateService,
string currentUserAddress,
Func<string, TokenInfo> tokenResolver);
public async Task<StateChangesResult> ExtractFromDecodedResultAsync(
DecodedProgramResult decodedResult,
ExecutionStateService stateService = null,
string currentUserAddress = null,
Func<string, Task<TokenInfo>> tokenResolverAsync = null,
CancellationToken cancellationToken = default);
public void ValidateTokenBalances(
StateChangesResult result,
Program program,
ExecutionStateService stateService,
Func<string, string, BigInteger> getErc20Balance = null,
Func<string, BigInteger, string> getErc721Owner = null,
Func<string, string, BigInteger, BigInteger> getErc1155Balance = null);
}
The extractor:
Transfer events from logs to identify ERC20/ERC721 transfersTransferSingle and TransferBatch events for ERC1155 transfersExecutionStateServiceValidateTokenBalances cross-checks extracted changes against actual state, detecting fee-on-transfer tokens and rebasing tokensFrom: StateChanges/StateChangesExtractor.cs:21-574
public class StateChangesResult
{
public List<BalanceChange> BalanceChanges { get; set; }
public DecodedCall RootCall { get; set; }
public List<DecodedLog> DecodedLogs { get; set; }
public DecodedProgramResult DecodedResult { get; set; }
public string Error { get; set; }
public List<ProgramTrace> Traces { get; set; }
public BigInteger GasUsed { get; set; }
public bool HasError { get; } // !string.IsNullOrEmpty(Error)
public bool HasBalanceChanges { get; }
public bool HasDecodedLogs { get; }
public bool HasTraces { get; }
public string ToSummaryString(); // Formatted balance changes, call tree, and events
}
From: StateChanges/StateChangesResult.cs:8-79
public enum BalanceChangeType
{
Native, // ETH transfers
ERC20, // ERC20 token transfers
ERC721, // ERC721 NFT transfers
ERC1155 // ERC1155 multi-token transfers
}
public enum BalanceValidationStatus
{
NotValidated, // Not yet validated against actual state
Verified, // Change matches actual state
FeeOnTransfer, // Actual change < expected (fee-on-transfer token)
Rebasing, // Actual change > expected (rebasing token)
OwnerMismatch, // ERC721 owner doesn't match expected
Mismatch // General mismatch
}
public class BalanceChange
{
// Identity
public string Address { get; set; }
public string AddressLabel { get; set; }
public bool IsCurrentUser { get; set; }
// Token info
public BalanceChangeType Type { get; set; }
public string TokenAddress { get; set; }
public string TokenSymbol { get; set; }
public int TokenDecimals { get; set; }
public BigInteger? TokenId { get; set; } // For ERC721/ERC1155
// Change amount
public BigInteger Change { get; set; } // Positive = received, negative = sent
public BigInteger? BalanceBefore { get; set; }
public BigInteger? BalanceAfter { get; set; }
// Validation
public BigInteger? ActualChange { get; set; }
public string ActualOwner { get; set; } // For ERC721 validation
public BalanceValidationStatus ValidationStatus { get; set; }
public bool HasDiscrepancy { get; }
// Display helpers
public string GetTokenIdentifier(); // "ETH", token address, or "address:tokenId"
public string GetDisplaySymbol(); // "ETH", "USDC", "NFT #42", etc.
public string GetAddressDisplay(); // Label or truncated address "0x1234...abcd"
}
From: StateChanges/BalanceChange.cs:1-85
public class TokenInfo
{
public string Symbol { get; set; }
public int Decimals { get; set; }
public TokenInfo();
public TokenInfo(string symbol, int decimals);
}
Used as the return type for token resolver functions passed to the extractor.
From: StateChanges/TokenInfo.cs:1-18
// After decoding a transaction execution result
var decoder = new ProgramResultDecoder(abiStorage);
var decoded = decoder.Decode(execResult, callInput, chainId);
// Extract state changes
var extractor = new StateChangesExtractor();
var stateChanges = extractor.ExtractFromDecodedResult(
decoded,
executionStateService,
currentUserAddress: "0xYourAddress");
// Print summary
Console.WriteLine(stateChanges.ToSummaryString());
// Inspect individual balance changes
foreach (var change in stateChanges.BalanceChanges)
{
var sign = change.Change >= 0 ? "+" : "";
Console.WriteLine(
$"{change.GetAddressDisplay()}: {sign}{change.Change} {change.GetDisplaySymbol()}" +
(change.IsCurrentUser ? " (you)" : ""));
}
// Async version with token metadata resolution
var stateChangesWithMetadata = await extractor.ExtractFromDecodedResultAsync(
decoded,
executionStateService,
currentUserAddress: "0xYourAddress",
tokenResolverAsync: async (address) =>
{
var info = await tokenService.GetTokenAsync(chainId, address);
return info != null ? new TokenInfo(info.Symbol, info.Decimals) : null;
});
The debugging subsystem provides a step-through debugger for EVM execution traces with Solidity source mapping support.
public class EVMDebuggerSession
{
public EVMDebuggerSession(IABIInfoStorage abiStorage);
// Loading
public void LoadFromProgram(Program executedProgram, BigInteger chainId);
public async Task LoadFromProgramAsync(Program executedProgram, BigInteger chainId);
public void LoadFromTrace(List<ProgramTrace> trace, BigInteger chainId);
public async Task LoadFromTraceAsync(List<ProgramTrace> trace, BigInteger chainId);
public void SetContractDebugInfo(string address, ABIInfo abiInfo);
// Navigation
public void StepForward();
public void StepBack();
public void GoToStep(int step);
public void GoToStart();
public void GoToEnd();
// State properties
public List<ProgramTrace> Trace { get; }
public int CurrentStep { get; }
public bool CanStepForward { get; }
public bool CanStepBack { get; }
public int TotalSteps { get; }
// Current step inspection
public ProgramTrace CurrentTrace { get; }
public ProgramInstruction CurrentInstruction { get; }
public List<string> CurrentStack { get; }
public string CurrentMemory { get; }
public Dictionary<string, string> CurrentStorage { get; }
public int CurrentDepth { get; }
public BigInteger CurrentGasCost { get; }
public string CurrentCodeAddress { get; }
public string CurrentProgramAddress { get; }
// Source mapping
public SourceLocation GetCurrentSourceLocation();
public SourceLocation GetSourceLocationForStep(int stepIndex);
public SourceLocation GetNearestSourceLocation(int stepIndex, int maxLookahead = 20);
public SourceLocation GetFunctionDeclarationLocation(string functionName, string codeAddress);
public List<int> FindStepsForSourceLine(string filePath, int lineNumber);
// Function/contract resolution
public string GetFunctionNameForStep(int stepIndex);
public string GetCurrentContractName();
public string GetContractNameForAddress(string address);
public ABIInfo GetABIInfoForAddress(string address);
// Call decoding
public CallStepInfo GetCallInfoForStep(int stepIndex);
// Source file access
public IEnumerable<string> GetSourceFiles();
public Dictionary<string, string> GetAllSourceFileContents();
// Display
public string ToDebugString(); // Full debug view (step, address, source, stack, storage)
public string ToSummaryString(); // One-line summary: "[step/total] OPCODE | file:line"
}
From: Debugging/EVMDebuggerSession.cs:14-802
Returned by GetCallInfoForStep when the current instruction is a CALL/STATICCALL/DELEGATECALL/CALLCODE opcode:
public class CallStepInfo
{
public string TargetAddress { get; set; }
public string ContractName { get; set; }
public string CallType { get; set; } // "CALL", "STATICCALL", etc.
public string Selector { get; set; } // "0x70a08231" etc.
public string FunctionName { get; set; }
public string FunctionSignature { get; set; }
public List<ParameterOutput> DecodedInputs { get; set; }
public string RawCalldata { get; set; }
}
From: Debugging/EVMDebuggerSession.cs:804-814
Extension methods for creating and navigating debug sessions:
public static class EVMDebuggerExtensions
{
// Create from Program
public static EVMDebuggerSession CreateDebugSession(
this Program program, IABIInfoStorage abiStorage, long chainId);
public static async Task<EVMDebuggerSession> CreateDebugSessionAsync(
this Program program, IABIInfoStorage abiStorage, long chainId);
// Create from trace list
public static EVMDebuggerSession CreateDebugSession(
this List<ProgramTrace> trace, IABIInfoStorage abiStorage, long chainId);
public static async Task<EVMDebuggerSession> CreateDebugSessionAsync(
this List<ProgramTrace> trace, IABIInfoStorage abiStorage, long chainId);
// Trace generation
public static string GenerateFullTraceString(this EVMDebuggerSession session);
public static string GenerateSourceAnnotatedTrace(this EVMDebuggerSession session);
// Source navigation
public static List<SourceLocation> GetUniqueSourceLocations(this EVMDebuggerSession session);
public static bool HasDebugInfo(this EVMDebuggerSession session);
public static IEnumerable<DebugStepInfo> EnumerateWithSource(this EVMDebuggerSession session);
public static void StepToNextSourceLine(this EVMDebuggerSession session);
public static void StepToPreviousSourceLine(this EVMDebuggerSession session);
}
DebugStepInfo combines step index, ProgramTrace, and resolved SourceLocation for enumeration.
From: Debugging/EVMDebuggerExtensions.cs:10-240
public class SourceLocation
{
public string FilePath { get; set; }
public int Position { get; set; } // Byte offset in source
public int Length { get; set; } // Length of source range
public string SourceCode { get; set; } // The source snippet
public string FullFileContent { get; set; }
public int LineNumber { get; set; }
public int ColumnNumber { get; set; }
public int SourceFileIndex { get; set; }
public string JumpType { get; set; }
public int ModifierDepth { get; set; }
public static SourceLocation FromSourceMap(SourceMap sourceMap, string filePath, string fileContent);
public string GetContextLines(int linesBefore = 2, int linesAfter = 2);
// Returns surrounding source lines with ">>> " marker on the current line
public override string ToString(); // "FilePath:LineNumber:ColumnNumber"
}
From: SourceInfo/SourceLocation.cs:1-99
// Create a debug session from a Program after EVM execution
var session = program.CreateDebugSession(abiStorage, chainId);
// Or from a TransactionExecutionResult's traces
// var session = execResult.Traces.CreateDebugSession(abiStorage, chainId);
// Step through the execution
while (session.CanStepForward)
{
var source = session.GetCurrentSourceLocation();
if (source != null)
{
Console.WriteLine($"Step {session.CurrentStep}: {source}");
Console.WriteLine(source.GetContextLines());
}
// Inspect call opcodes
var callInfo = session.GetCallInfoForStep(session.CurrentStep);
if (callInfo != null)
{
Console.WriteLine($" CALL -> {callInfo.ContractName ?? callInfo.TargetAddress}");
Console.WriteLine($" Function: {callInfo.FunctionName ?? callInfo.Selector}");
}
session.StepForward();
}
// Or step by source line (skips opcodes that map to the same line)
session.GoToStart();
while (session.CanStepForward)
{
session.StepToNextSourceLine();
Console.WriteLine(session.ToSummaryString());
}
// Generate a full annotated trace
Console.WriteLine(session.GenerateSourceAnnotatedTrace());
// Full debug view at current step
Console.WriteLine(session.ToDebugString());
Low-level gas calculation functions used by TransactionExecutor and available for standalone use.
public static class IntrinsicGasCalculator
{
// Constants
public const int G_TRANSACTION = 21000;
public const int G_TXDATAZERO = 4;
public const int G_TXDATANONZERO = 16;
public const int G_TXCREATE = 32000;
public const int G_CODEDEPOSIT = 200;
public const int G_ACCESS_LIST_ADDRESS = 2400;
public const int G_ACCESS_LIST_STORAGE = 1900;
public const int G_INITCODE_WORD = 2;
public const int G_FLOOR_PER_TOKEN = 10;
public const int G_TOKENS_PER_NONZERO = 4;
public const int GAS_PER_BLOB = 131072;
public const int MIN_BASE_FEE_PER_BLOB_GAS = 1;
public const int BLOB_BASE_FEE_UPDATE_FRACTION = 3338477;
// Intrinsic gas: base 21000 + calldata costs + access list + create + initcode words
public static BigInteger CalculateIntrinsicGas(
byte[] data, bool isContractCreation, IList<AccessListEntry> accessList);
// EIP-7623: Floor gas limit based on calldata token count
public static BigInteger CalculateFloorGasLimit(byte[] data, bool isContractCreation);
// Calldata token count: zero_bytes + (nonzero_bytes * 4)
public static BigInteger CalculateTokensInCalldata(byte[] data);
// EIP-4844 blob gas
public static BigInteger CalculateBlobBaseFee(BigInteger excessBlobGas);
public static BigInteger CalculateBlobGasCost(int blobCount, BigInteger blobBaseFee);
// Contract creation helpers
public static BigInteger CalculateCodeDepositGas(int codeLength); // codeLength * 200
public static BigInteger CalculateMaxRefund(BigInteger gasUsed); // gasUsed / 5
}
From: Gas/IntrinsicGasCalculator.cs:1-129
Comprehensive gas cost constants organized by EIP:
| Group | Constants |
|---|---|
| EIP-2929 (Berlin) | COLD_SLOAD_COST (2100), COLD_ACCOUNT_ACCESS_COST (2600), WARM_STORAGE_READ_COST (100) |
| EIP-2200/2929 SSTORE | SSTORE_SET (20000), SSTORE_RESET (2900), SSTORE_NOOP (100), SSTORE_SENTRY (2300) |
| EIP-3529 Refunds | SSTORE_CLEARS_SCHEDULE (4800), REFUND_QUOTIENT (5), SSTORE_SET_REFUND (19900), SSTORE_RESET_REFUND (2800) |
| Base Opcode Costs | G_ZERO (0), G_JUMPDEST (1), G_BASE (2), G_VERYLOW (3), G_LOW (5), G_MID (8), G_HIGH (10), G_BLOCKHASH (20) |
| EXP | EXP_BASE (10), EXP_BYTE (50) |
| Memory | COPY_BASE (3), COPY_PER_WORD (3), MEMORY_BASE (3), QUAD_COEFF_DIV (512) |
| KECCAK256 | KECCAK256_BASE (30), KECCAK256_PER_WORD (6) |
| LOG | LOG_BASE (375), LOG_PER_TOPIC (375), LOG_PER_BYTE (8) |
| CREATE | CREATE_BASE (32000), CREATE2_HASH_PER_WORD (6), CREATE_DATA_GAS (200), INIT_CODE_WORD_GAS (2) |
| CALL | G_CALL (700), CALL_VALUE_TRANSFER (9000), CALL_NEW_ACCOUNT (25000), CALL_STIPEND (2300), GAS_DIVISOR (64) |
| Limits | MAX_CALL_DEPTH (1024), MAX_CODE_SIZE (24576), MAX_INITCODE_SIZE (49152) |
| Transient (EIP-1153) | TLOAD_COST (100), TSTORE_COST (100) |
| EIP-7623 Floor | TX_FLOOR_PER_TOKEN (10), TX_TOKENS_PER_NON_ZERO_BYTE (4) |
| Precompiles | ECRECOVER_GAS (3000), SHA256_BASE_GAS (60), KZG_POINT_EVALUATION_GAS (50000), BLS12-381 costs |
From: Gas/GasConstants.cs:1-121
// Calculate intrinsic gas for a transaction
var calldata = "0xa9059cbb000000000000000000000000...".HexToByteArray();
var intrinsicGas = IntrinsicGasCalculator.CalculateIntrinsicGas(
calldata,
isContractCreation: false,
accessList: null);
// Result: 21000 + (4 * zeroBytes) + (16 * nonZeroBytes)
// EIP-7623 floor gas check
var floorGas = IntrinsicGasCalculator.CalculateFloorGasLimit(calldata, isContractCreation: false);
var minGasRequired = BigInteger.Max(intrinsicGas, floorGas);
// Blob gas calculation (EIP-4844)
var blobBaseFee = IntrinsicGasCalculator.CalculateBlobBaseFee(excessBlobGas);
var blobGasCost = IntrinsicGasCalculator.CalculateBlobGasCost(blobCount: 3, blobBaseFee);
The precompile system provides pluggable support for EVM precompiled contracts, allowing custom precompiles to be added alongside built-in ones.
public interface IPrecompileProvider
{
IEnumerable<string> GetHandledAddresses();
bool CanHandle(string address);
BigInteger GetGasCost(string address, byte[] data);
byte[] Execute(string address, byte[] data);
}
From: Execution/IPrecompileProvider.cs:6-12
Wraps the built-in precompile execution engine for a range of precompile addresses:
public class BuiltInPrecompileProvider : IPrecompileProvider
{
public static BuiltInPrecompileProvider Cancun(); // Addresses 0x01-0x0a (1-10)
public static BuiltInPrecompileProvider Prague(); // Addresses 0x01-0x11 (1-17, includes BLS)
public BuiltInPrecompileProvider(int start, int end);
}
Cancun includes: ecRecover, SHA-256, RIPEMD-160, identity, modexp, ecAdd, ecMul, ecPairing, blake2f, KZG point evaluation.
Prague adds: BLS12-381 operations (G1ADD, G1MSM, G2ADD, G2MSM, pairing, MAP_FP_TO_G1, MAP_FP2_TO_G2).
From: Execution/BuiltInPrecompileProvider.cs:7-38
Composes multiple providers, delegating to the first provider that can handle a given address:
public class CompositePrecompileProvider : IPrecompileProvider
{
public CompositePrecompileProvider(params IPrecompileProvider[] providers);
}
From: Execution/CompositePrecompileProvider.cs:7-37
public static class HardforkConfigExtensions
{
public static HardforkConfig WithPrecompileProviders(
this HardforkConfig config,
params IPrecompileProvider[] additionalProviders);
}
Creates a new HardforkConfig with a CompositePrecompileProvider that combines the additional providers with the config's existing provider. Additional providers take priority.
From: HardforkConfigExtensions.cs:1-28
// Implement a custom precompile
public class MyCustomPrecompile : IPrecompileProvider
{
private const string ADDRESS = "0x0000000000000000000000000000000000000100";
public IEnumerable<string> GetHandledAddresses() => new[] { ADDRESS };
public bool CanHandle(string address) => address.Equals(ADDRESS, StringComparison.OrdinalIgnoreCase);
public BigInteger GetGasCost(string address, byte[] data) => 1000;
public byte[] Execute(string address, byte[] data)
{
// Custom precompile logic
return data;
}
}
// Use with TransactionExecutor
var config = HardforkConfig.Prague.WithPrecompileProviders(new MyCustomPrecompile());
var executor = new TransactionExecutor(config);
// Or pass directly to the constructor
var executor2 = new TransactionExecutor(
config: HardforkConfig.Prague,
customPrecompileProvider: new MyCustomPrecompile());
Core dependencies:
Core Execution:
EVMSimulator.cs - Main execution engineProgram.cs - Program state (stack, memory, instructions)ProgramContext.cs - Execution contextProgramResult.cs - Execution resultsProgramTrace.cs - Execution tracesInstruction.cs - Opcode definitionsTransaction Pipeline:
TransactionExecutor.cs - Full transaction execution pipelineTransactionExecutionContext.cs - Transaction input parameters and block contextTransactionExecutionResult.cs - Execution results with traces and inner callsHardforkConfig.cs - Hardfork presets (Cancun, Prague)HardforkConfigExtensions.cs - Extension methods for adding precompile providersDecoding:
Decoding/ProgramResultDecoder.cs - ABI-aware result decoderDecoding/DecodedProgramResult.cs - Decoded execution result containerDecoding/DecodedCall.cs - Decoded function call with parametersDecoding/DecodedLog.cs - Decoded event log with parametersDecoding/DecodedError.cs - Decoded revert error with parametersState Changes:
StateChanges/StateChangesExtractor.cs - Balance change extraction from decoded resultsStateChanges/StateChangesResult.cs - State changes result containerStateChanges/BalanceChange.cs - Individual balance change with validationStateChanges/TokenInfo.cs - Token metadata for resolver functionsStateChanges/IStateChangesExtractor.cs - Extractor interfaceDebugging:
Debugging/EVMDebuggerSession.cs - Step-through debugger with source mappingDebugging/EVMDebuggerExtensions.cs - Extension methods for creating debug sessionsGas Calculation:
Gas/OpcodeGasTable.cs - Comprehensive gas costsGas/IntrinsicGasCalculator.cs - Transaction intrinsic gas and EIP-7623 floorGas/GasConstants.cs - Gas cost constants organized by EIPPrecompiles:
Execution/IPrecompileProvider.cs - Precompile provider interfaceExecution/BuiltInPrecompileProvider.cs - Built-in precompile provider (Cancun/Prague)Execution/CompositePrecompileProvider.cs - Multi-provider compositionBlockchain State:
BlockchainState/ExecutionStateService.cs - State managementBlockchainState/INodeDataService.cs - State provider interfaceBlockchainState/RpcNodeDataService.cs - RPC-based state providerBlockchainState/AccountExecutionState.cs - Account state trackingBlockchainState/AccountExecutionBalance.cs - Balance trackingBytecode Utilities:
ProgramInstruction.cs - Instruction representationProgramInstructionsUtils.cs - Parsing and disassemblySource Mapping:
SourceInfo/SourceMap.cs - Solidity source mappingSourceInfo/SourceMapUtil.cs - Source map utilitiesSourceInfo/SourceLocation.cs - Resolved source location with contextExample test pattern from EvmSimulatorTests.cs:
[Fact]
public async Task TestEVMOperation()
{
var bytecode = "..."; // Hex bytecode
var expected = "..."; // Expected stack top result
var vm = new EVMSimulator();
var program = new Program(bytecode.HexToByteArray());
// Execute N steps
for (var i = 0; i < numberOfSteps; i++)
{
await vm.StepAsync(program, i);
}
Assert.Equal(expected.ToUpper(), program.StackPeek().ToHex().ToUpper());
}
Nethereum is licensed under the MIT License.
StateChangesPreviewService for transaction preview| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 net5.0 was computed. net5.0-windows net5.0-windows was computed. net6.0 net6.0 is compatible. net6.0-android net6.0-android was computed. net6.0-ios net6.0-ios was computed. net6.0-maccatalyst net6.0-maccatalyst was computed. net6.0-macos net6.0-macos was computed. net6.0-tvos net6.0-tvos was computed. net6.0-windows net6.0-windows was computed. net7.0 net7.0 was computed. net7.0-android net7.0-android was computed. net7.0-ios net7.0-ios was computed. net7.0-maccatalyst net7.0-maccatalyst was computed. net7.0-macos net7.0-macos was computed. net7.0-tvos net7.0-tvos was computed. net7.0-windows net7.0-windows was computed. net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 is compatible. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 netcoreapp2.0 was computed. netcoreapp2.1 netcoreapp2.1 was computed. netcoreapp2.2 netcoreapp2.2 was computed. netcoreapp3.0 netcoreapp3.0 was computed. netcoreapp3.1 netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 netstandard2.0 is compatible. netstandard2.1 netstandard2.1 was computed. |
| .NET Framework | net451 net451 is compatible. net452 net452 was computed. net46 net46 was computed. net461 net461 is compatible. net462 net462 was computed. net463 net463 was computed. net47 net47 was computed. net471 net471 was computed. net472 net472 was computed. net48 net48 was computed. net481 net481 was computed. |
| MonoAndroid | monoandroid monoandroid was computed. |
| MonoMac | monomac monomac was computed. |
| MonoTouch | monotouch monotouch was computed. |
| Tizen | tizen40 tizen40 was computed. tizen60 tizen60 was computed. |
| Xamarin.iOS | xamarinios xamarinios was computed. |
| Xamarin.Mac | xamarinmac xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos xamarinwatchos was computed. |
Showing the top 5 NuGet packages that depend on Nethereum.EVM:
| Package | Downloads |
|---|---|
|
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.ChainStateVerification
Verified execution-state primitives (account/storage/receipt proofs rooted in the light client). |
|
|
Nethereum.Wallet
Core wallet services for managing accounts, vaults, and configuration across the Nethereum stack. |
|
|
Nethereum.CoreChain
Nethereum CoreChain - Core blockchain infrastructure for state, transactions, and receipts root management |
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 6.1.0 | 1,512 | 3/25/2026 |
| 6.0.4 | 579 | 3/18/2026 |
| 6.0.3 | 528 | 3/18/2026 |
| 6.0.1 | 586 | 3/17/2026 |
| 6.0.0 | 559 | 3/16/2026 |
| 5.8.0 | 358 | 1/6/2026 |
| 5.0.0 | 478 | 5/28/2025 |
| 4.29.0 | 454 | 2/10/2025 |
| 4.28.0 | 372 | 1/7/2025 |
| 4.27.1 | 383 | 12/24/2024 |
| 4.27.0 | 357 | 12/24/2024 |
| 4.26.0 | 446 | 10/1/2024 |
| 4.25.0 | 393 | 9/19/2024 |
| 4.21.4 | 455 | 8/9/2024 |
| 4.21.3 | 390 | 7/22/2024 |
| 4.21.2 | 467 | 6/26/2024 |
| 4.21.1 | 405 | 6/26/2024 |
| 4.21.0 | 445 | 6/18/2024 |
| 4.20.0 | 849 | 3/28/2024 |
| 4.19.0 | 527 | 2/16/2024 |