![]() |
VOOZH | about |
dotnet add package Nethereum.EVM.Contracts --version 6.1.0
NuGet\Install-Package Nethereum.EVM.Contracts -Version 6.1.0
<PackageReference Include="Nethereum.EVM.Contracts" Version="6.1.0" />
<PackageVersion Include="Nethereum.EVM.Contracts" Version="6.1.0" />Directory.Packages.props
<PackageReference Include="Nethereum.EVM.Contracts" />Project file
paket add Nethereum.EVM.Contracts --version 6.1.0
#r "nuget: Nethereum.EVM.Contracts, 6.1.0"
#:package Nethereum.EVM.Contracts@6.1.0
#addin nuget:?package=Nethereum.EVM.Contracts&version=6.1.0Install as a Cake Addin
#tool nuget:?package=Nethereum.EVM.Contracts&version=6.1.0Install as a Cake Tool
Nethereum.EVM.Contracts provides high-level contract simulators built on top of Nethereum.EVM for testing and analyzing smart contract behavior without broadcasting transactions.
This package offers specialized contract simulators that leverage the EVM execution engine to:
Status: Production - suitable for testing, debugging, simulation, and analysis.
dotnet add package Nethereum.EVM.Contracts
Complete ERC20 token operation simulation with:
High-level simulator for ERC20 token contracts. Located in ERC20/ERC20ContractSimulator.cs:19-197.
Constructor:
public ERC20ContractSimulator(
IWeb3 web3,
BigInteger chainId,
string contractAddress,
byte[] code = null
)
Properties:
Web3 - Web3 instance for RPC accessChainId - Network chain IDContractAddress - ERC20 token contract addressResult from SimulateTransferAndBalanceStateAsync. Located in ERC20ContractSimulator.cs:35-47.
Properties:
BalanceSenderBefore - Sender balance before transferBalanceSenderStorageAfter - Sender balance in storage after transferBalanceSenderAfter - Sender balance from balanceOf after transferBalanceReceiverBefore - Receiver balance before transferBalanceReceiverStorageAfter - Receiver balance in storage after transferBalanceReceiverAfter - Receiver balance from balanceOf after transferTransferLogs - Event logs emitted during transferusing Nethereum.EVM.Contracts.ERC20;
using Nethereum.Web3;
var web3 = new Web3("https://mainnet.infura.io/v3/YOUR_KEY");
// USDC contract on mainnet
var usdcAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
var simulator = new ERC20ContractSimulator(web3, chainId: 1, usdcAddress);
// Simulate transfer from address1 to address2
var senderAddress = "0x0000000000000000000000000000000000000001";
var receiverAddress = "0x0000000000000000000000000000000000000025";
var amount = 100; // 100 USDC (6 decimals)
var result = await simulator.SimulateTransferAndBalanceStateAsync(
senderAddress,
receiverAddress,
amount
);
Console.WriteLine($"Sender balance before: {result.BalanceSenderBefore}");
Console.WriteLine($"Sender balance after: {result.BalanceSenderAfter}");
Console.WriteLine($"Receiver balance before: {result.BalanceReceiverBefore}");
Console.WriteLine($"Receiver balance after: {result.BalanceReceiverAfter}");
// Verify balance changes
Assert.Equal(result.BalanceSenderAfter, result.BalanceSenderBefore - amount);
Assert.Equal(result.BalanceReceiverAfter, result.BalanceReceiverBefore + amount);
// Check storage consistency
Assert.Equal(result.BalanceSenderStorageAfter, result.BalanceSenderAfter);
Assert.Equal(result.BalanceReceiverStorageAfter, result.BalanceReceiverAfter);
// Inspect transfer logs
foreach (var log in result.TransferLogs)
{
Console.WriteLine($"Transfer event: {log.Topics[0]}");
}
From test: Erc20EVMContractSimulatorAndStorage.cs:54-67
using Nethereum.EVM.Contracts.ERC20;
using Nethereum.EVM.BlockchainState;
using Nethereum.RPC.Eth.DTOs;
using Nethereum.Web3;
var web3 = new Web3("https://mainnet.infura.io/v3/YOUR_KEY");
var tokenAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; // USDC
var simulator = new ERC20ContractSimulator(web3, chainId: 1, tokenAddress);
// Get current block
var blockNumber = await web3.Eth.Blocks.GetBlockNumber.SendRequestAsync();
// Create state service
var nodeDataService = new RpcNodeDataService(
web3.Eth,
new BlockParameter(blockNumber)
);
var stateService = new ExecutionStateService(nodeDataService);
// Simulate balance query
var ownerAddress = "0x0000000000000000000000000000000000000001";
var balance = await simulator.SimulateGetBalanceAsync(ownerAddress, stateService);
Console.WriteLine($"Balance of {ownerAddress}: {balance}");
From method: ERC20ContractSimulator.cs:117-133
This powerful feature reverse-engineers where ERC20 balances are stored by simulating balanceOf and comparing storage values.
using Nethereum.EVM.Contracts.ERC20;
using Nethereum.Web3;
var web3 = new Web3("https://mainnet.infura.io/v3/YOUR_KEY");
// USDC contract
var usdcAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
var simulator = new ERC20ContractSimulator(web3, chainId: 1, usdcAddress);
// Find an address with a non-zero balance
var addressWithBalance = "0x0000000000000000000000000000000000000001";
// Calculate the storage slot where balances are stored
// Tests up to 100 slots by default
var balanceSlot = await simulator.CalculateMappingBalanceSlotAsync(
addressWithBalance,
numberOfSlotsToTry: 100
);
Console.WriteLine($"Balance mapping is at storage slot: {balanceSlot}");
// Output: Balance mapping is at storage slot: 9 (for USDC)
From test: Erc20EVMContractSimulatorAndStorage.cs:34-50
using Nethereum.EVM.Contracts.ERC20;
using Nethereum.EVM.BlockchainState;
using Nethereum.RPC.Eth.DTOs;
using Nethereum.Web3;
using System.Numerics;
var web3 = new Web3("https://mainnet.infura.io/v3/YOUR_KEY");
var tokenAddress = "0xYourTokenAddress";
var simulator = new ERC20ContractSimulator(web3, chainId: 1, tokenAddress);
// Setup state service
var blockNumber = await web3.Eth.Blocks.GetBlockNumber.SendRequestAsync();
var nodeDataService = new RpcNodeDataService(web3.Eth, new BlockParameter(blockNumber));
var stateService = new ExecutionStateService(nodeDataService);
// Simulate transfer
var senderAddress = "0xSenderAddress";
var receiverAddress = "0xReceiverAddress";
var amount = BigInteger.Parse("1000000"); // 1 token (6 decimals)
var programResult = await simulator.SimulateTransferAsync(
senderAddress,
receiverAddress,
amount,
stateService
);
if (programResult.IsRevert)
{
Console.WriteLine($"Transfer would revert: {programResult.GetRevertMessage()}");
}
else
{
Console.WriteLine("Transfer simulation successful");
Console.WriteLine($"Logs generated: {programResult.Logs.Count}");
// Inspect Transfer event
foreach (var log in programResult.Logs)
{
if (log.Topics.Length > 0)
{
Console.WriteLine($"Event signature: {log.Topics[0]}");
}
}
}
From method: ERC20ContractSimulator.cs:96-115
Compare direct storage reads with contract function calls to validate storage layout:
using Nethereum.Contracts.ContractStorage;
using Nethereum.Web3;
var web3 = new Web3("https://mainnet.infura.io/v3/YOUR_KEY");
var tokenAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; // USDC
// Get balance via smart contract call
var erc20Service = web3.Eth.ERC20.GetContractService(tokenAddress);
var address = "0x0000000000000000000000000000000000000001";
var balanceFromContract = await erc20Service.BalanceOfQueryAsync(address);
// Get balance from direct storage read (slot 9 for USDC)
var balanceFromStorage = await erc20Service.GetBalanceFromStorageAsync(address, slot: 9);
// Verify they match
Assert.Equal(balanceFromContract, balanceFromStorage);
Console.WriteLine($"Balance from contract call: {balanceFromContract}");
Console.WriteLine($"Balance from storage: {balanceFromStorage}");
Console.WriteLine("Storage layout validated!");
From test: Erc20EVMContractSimulatorAndStorage.cs:72-80
The CalculateMappingBalanceSlotAsync method discovers where balances are stored by: (Located in ERC20ContractSimulator.cs:136-193)
balanceOf(address) call to get expected balancekeccak256(address || slot)Example calculation:
// For mapping(address => uint256) balances at slot 9:
// Storage key = keccak256(leftPad32(address) + leftPad32(9))
var storageKey = StorageUtil.CalculateMappingAddressStorageKeyAsBigInteger(
address,
slot: 9
);
The SimulateTransferAndBalanceStateAsync method: (Located in ERC20ContractSimulator.cs:59-94)
Provide pre-fetched contract bytecode to avoid RPC calls:
var web3 = new Web3("https://mainnet.infura.io/v3/YOUR_KEY");
var contractAddress = "0xYourContract";
// Fetch bytecode once
var bytecode = await web3.Eth.GetCode.SendRequestAsync(contractAddress);
// Reuse bytecode for multiple simulations
var simulator = new ERC20ContractSimulator(
web3,
chainId: 1,
contractAddress,
code: bytecode.HexToByteArray()
);
// Multiple simulations without refetching code
var result1 = await simulator.SimulateTransferAndBalanceStateAsync(addr1, addr2, 100);
var result2 = await simulator.SimulateTransferAndBalanceStateAsync(addr3, addr4, 200);
Simulate at a specific block height:
using Nethereum.Hex.HexTypes;
var web3 = new Web3("https://mainnet.infura.io/v3/YOUR_KEY");
var simulator = new ERC20ContractSimulator(web3, 1, tokenAddress);
// Simulate at specific block
var historicalBlock = new HexBigInteger(18_000_000);
var slot = await simulator.CalculateMappingBalanceSlotAsync(
address,
numberOfSlotsToTry: 1000,
blockNumber: historicalBlock
);
From method: ERC20ContractSimulator.cs:136-143
Compare actual gas with simulated gas:
using Nethereum.Contracts.Standards.ERC20.ContractDefinition;
var web3 = new Web3("https://mainnet.infura.io/v3/YOUR_KEY");
var contractAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
// Estimate gas via RPC
var contractHandler = web3.Eth.GetContractHandler(contractAddress);
var balanceOfFunction = new BalanceOfFunction { Owner = address };
var gasEstimate = await contractHandler.EstimateGasAsync(balanceOfFunction);
Console.WriteLine($"Gas estimate from RPC: {gasEstimate.Value}");
// Simulate with gas tracking
var simulator = new ERC20ContractSimulator(web3, 1, contractAddress);
var blockNumber = await web3.Eth.Blocks.GetBlockNumber.SendRequestAsync();
var nodeDataService = new RpcNodeDataService(web3.Eth, new BlockParameter(blockNumber));
var stateService = new ExecutionStateService(nodeDataService);
var balance = await simulator.SimulateGetBalanceAsync(address, stateService);
// Access trace for gas analysis
// Note: Requires tracing enabled in ExecuteAsync
Validate transfers before broadcasting:
var simulator = new ERC20ContractSimulator(web3, 1, tokenAddress);
// Check if transfer would succeed
var result = await simulator.SimulateTransferAndBalanceStateAsync(
senderAddress,
receiverAddress,
amount
);
// Check if the transfer was successful (sufficient balance, no revert)
if (result.BalanceSenderAfter > result.BalanceSenderBefore)
{
throw new Exception("Unexpected sender balance increase");
}
// Validate expected balance changes
var expectedSenderBalance = result.BalanceSenderBefore - amount;
var expectedReceiverBalance = result.BalanceReceiverBefore + amount;
if (result.BalanceSenderAfter != expectedSenderBalance ||
result.BalanceReceiverAfter != expectedReceiverBalance)
{
throw new Exception("Unexpected balance changes");
}
// Validate events
if (!result.TransferLogs.Any(log =>
log.Topics.Length > 0 &&
log.Topics[0].ToString() == "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"))
{
throw new Exception("Missing Transfer event");
}
Reverse-engineer contract storage for data extraction:
var simulator = new ERC20ContractSimulator(web3, 1, contractAddress);
// Find balance storage slot
var balanceSlot = await simulator.CalculateMappingBalanceSlotAsync(knownAddress);
Console.WriteLine($"Balances stored at slot: {balanceSlot}");
// Now you can directly read any address's balance from storage
var anyAddress = "0xAnyAddress";
var storageKey = StorageUtil.CalculateMappingAddressStorageKeyAsBigInteger(
anyAddress,
(ulong)balanceSlot
);
var balanceBytes = await web3.Eth.GetStorageAt.SendRequestAsync(
contractAddress,
storageKey.ToHexBigInteger()
);
var balance = new IntTypeDecoder().DecodeBigInteger(balanceBytes);
Console.WriteLine($"Balance of {anyAddress}: {balance}");
Test new implementations before deployment:
// Fetch current implementation bytecode
var currentCode = await web3.Eth.GetCode.SendRequestAsync(tokenAddress);
var currentSimulator = new ERC20ContractSimulator(web3, 1, tokenAddress, currentCode.HexToByteArray());
// Simulate with current implementation
var currentResult = await currentSimulator.SimulateTransferAndBalanceStateAsync(addr1, addr2, 100);
// Load new implementation bytecode
var newCode = File.ReadAllBytes("NewImplementation.bin");
var newSimulator = new ERC20ContractSimulator(web3, 1, tokenAddress, newCode);
// Simulate with new implementation
var newResult = await newSimulator.SimulateTransferAndBalanceStateAsync(addr1, addr2, 100);
// Compare results
Assert.Equal(currentResult.BalanceSenderAfter, newResult.BalanceSenderAfter);
Assert.Equal(currentResult.BalanceReceiverAfter, newResult.BalanceReceiverAfter);
Simulate complex transfer sequences:
var simulator = new ERC20ContractSimulator(web3, 1, tokenAddress);
var blockNumber = await web3.Eth.Blocks.GetBlockNumber.SendRequestAsync();
var nodeDataService = new RpcNodeDataService(web3.Eth, new BlockParameter(blockNumber));
// Reuse state service across simulations (preserves state changes)
var stateService = new ExecutionStateService(nodeDataService);
// Simulation 1: A -> B
await simulator.SimulateTransferAsync("0xA", "0xB", 100, stateService);
// Simulation 2: B -> C (uses updated state from simulation 1)
await simulator.SimulateTransferAsync("0xB", "0xC", 50, stateService);
// Simulation 3: C -> A (uses updated state from simulations 1 & 2)
await simulator.SimulateTransferAsync("0xC", "0xA", 25, stateService);
// Query final balances
var balanceA = await simulator.SimulateGetBalanceAsync("0xA", stateService);
var balanceB = await simulator.SimulateGetBalanceAsync("0xB", stateService);
var balanceC = await simulator.SimulateGetBalanceAsync("0xC", stateService);
Required packages:
This simulator has the following limitations:
The CalculateMappingBalanceSlotAsync method:
numberOfSlotsToTry attempts (default 10,000)Not designed for:
eth_estimateGas for production)Contract Simulators:
ERC20/ERC20ContractSimulator.cs - ERC20 token simulatorTest Files:
tests/Nethereum.Contracts.IntegrationTests/EVM/Erc20EVMContractSimulatorAndStorage.cs - Integration testsPlanned additions:
Nethereum is licensed under the MIT License.
| 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. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 6.1.0 | 685 | 3/25/2026 |
| 6.0.4 | 166 | 3/18/2026 |
| 6.0.3 | 123 | 3/18/2026 |
| 6.0.1 | 135 | 3/17/2026 |
| 6.0.0 | 130 | 3/16/2026 |
| 5.8.0 | 159 | 1/6/2026 |
| 5.0.0 | 325 | 5/28/2025 |
| 4.29.0 | 310 | 2/10/2025 |
| 4.28.0 | 300 | 1/7/2025 |
| 4.27.1 | 268 | 12/24/2024 |
| 4.27.0 | 286 | 12/24/2024 |
| 4.26.0 | 291 | 10/1/2024 |
| 4.25.0 | 294 | 9/19/2024 |
| 4.21.4 | 333 | 8/9/2024 |
| 4.21.3 | 266 | 8/5/2024 |
| 4.21.2 | 300 | 6/26/2024 |
| 4.21.1 | 301 | 6/26/2024 |
| 4.21.0 | 323 | 6/18/2024 |
| 4.20.0 | 731 | 3/28/2024 |
| 4.19.0 | 374 | 2/16/2024 |