EVMole is a powerful library that extracts information from Ethereum Virtual Machine (EVM) bytecode, including function selectors, arguments, state mutability, and storage layout, even for unverified contracts.
- Multi-language support: Available as JavaScript, Rust, Python, and Go libraries.
- High accuracy and performance: Outperforms existing tools.
- Broad compatibility: Tested with both Solidity and Vyper compiled contracts.
- Lightweight: Clean codebase with minimal external dependencies.
- Unverified contract analysis: Extracts information even from unverified bytecode.
API documentation and usage examples (node, vite, webpack, parcel, esbuild)
$ npm i evmoleimport { contractInfo } from 'evmole'
const code = '0x6080604052348015600e575f80fd5b50600436106030575f3560e01c80632125b65b146034578063b69ef8a8146044575b5f80fd5b6044603f3660046046565b505050565b005b5f805f606084860312156057575f80fd5b833563ffffffff811681146069575f80fd5b925060208401356001600160a01b03811681146083575f80fd5b915060408401356001600160e01b0381168114609d575f80fd5b80915050925092509256'
console.log( contractInfo(code, {selectors:true, arguments:true, stateMutability:true}) )
// {
// functions: [
// {
// selector: '2125b65b',
// bytecodeOffset: 52,
// arguments: 'uint32,address,uint224',
// stateMutability: 'pure'
// },
// ...Documentation is available on docs.rs
let code = hex::decode("6080604052348015600e575f80fd5b50600436106030575f3560e01c80632125b65b146034578063b69ef8a8146044575b5f80fd5b6044603f3660046046565b505050565b005b5f805f606084860312156057575f80fd5b833563ffffffff811681146069575f80fd5b925060208401356001600160a01b03811681146083575f80fd5b915060408401356001600160e01b0381168114609d575f80fd5b80915050925092509256").unwrap();
println!("{:?}", evmole::contract_info(
evmole::ContractInfoArgs::new(&code)
.with_selectors()
.with_arguments()
.with_state_mutability()
)
);
// Contract {
// functions: Some([
// Function {
// selector: [33, 37, 182, 91],
// bytecode_offset: 52,
// arguments: Some([Uint(32), Address, Uint(224)]),
// state_mutability: Some(Pure)
// },
// ...$ pip install evmole --upgradefrom evmole import contract_info
code = '0x6080604052348015600e575f80fd5b50600436106030575f3560e01c80632125b65b146034578063b69ef8a8146044575b5f80fd5b6044603f3660046046565b505050565b005b5f805f606084860312156057575f80fd5b833563ffffffff811681146069575f80fd5b925060208401356001600160a01b03811681146083575f80fd5b915060408401356001600160e01b0381168114609d575f80fd5b80915050925092509256'
print( contract_info(code, selectors=True, arguments=True, state_mutability=True) )
# Contract(
# functions=[
# Function(
# selector=2125b65b,
# bytecode_offset=52,
# arguments=uint32,address,uint224,
# state_mutability=pure),
# ...$ go get github.com/cdump/evmole/gopackage main
import (
"context"
"encoding/hex"
"fmt"
"github.com/cdump/evmole/go"
)
func main() {
code, _ := hex.DecodeString("6080604052348015600e575f80fd5b50600436106030575f3560e01c80632125b65b146034578063b69ef8a8146044575b5f80fd5b6044603f3660046046565b505050565b005b5f805f606084860312156057575f80fd5b833563ffffffff811681146069575f80fd5b925060208401356001600160a01b03811681146083575f80fd5b915060408401356001600160e01b0381168114609d575f80fd5b80915050925092509256")
info, _ := evmole.ContractInfo(context.Background(), code, evmole.Options{
Selectors: true,
Arguments: true,
StateMutability: true,
})
for _, fn := range info.Functions {
fmt.Printf("%s: %s @ %d\n", fn.Selector, *fn.Arguments, fn.BytecodeOffset)
}
// 2125b65b: uint32,address,uint224 @ 52
// b69ef8a8: @ 68
}Foundry's cast uses the Rust implementation of EVMole
$ cast selectors $(cast code 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)
0x06fdde03 view
0x095ea7b3 address,uint256 nonpayable
0x18160ddd view
0x23b872dd address,address,uint256 nonpayable
...
$ cast selectors --resolve $(cast code 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)
0x06fdde03 view name()
0x095ea7b3 address,uint256 nonpayable approve(address,uint256)
0x18160ddd view totalSupply()
0x23b872dd address,address,uint256 nonpayable transferFrom(address,address,uint256)
...FP/FN - False Positive/False Negative errors; smaller is better
| Dataset | evmole rs · js · py · go | whatsabi | sevm | evmhound | heimdall | |
| largest1k 1000 addresses 24427 functions |
FP addrs | 1 🥈 | 0 🥇 | 0 🥇 | 75 | 18 |
| FN addrs | 0 🥇 | 0 🥇 | 0 🥇 | 40 | 103 | |
| FP funcs | 192 🥈 | 0 🥇 | 0 🥇 | 720 | 600 | |
| FN funcs | 0 🥇 | 0 🥇 | 0 🥇 | 191 | 114 | |
| Time | 18ms · 0.3s · 21ms · 0.1s | 2.3s | 30s(*) | 56ms | 371s(*) | |
| random50k 50000 addresses 1171102 functions |
FP addrs | 1 🥇 | 43 | 1 | 693 | 3 |
| FN addrs | 9 🥇 | 11 | 10 | 2903 | 4669 | |
| FP funcs | 3 🥇 | 51 | 3 | 10798 | 29 | |
| FN funcs | 10 🥇 | 12 | 11 | 3538 | 4943 | |
| Time | 0.3s · 2.6s · 0.5s · 5.7s | 30s | 440s(*) | 1.6s | 8684s(*) | |
| vyper 780 addresses 21244 functions |
FP addrs | 0 🥇 | 30 | 0 | 19 | 0 |
| FN addrs | 0 🥇 | 780 | 0 | 300 | 780 | |
| FP funcs | 0 🥇 | 30 | 0 | 19 | 0 | |
| FN funcs | 0 🥇 | 21244 | 0 | 8273 | 21244 | |
| Time | 10ms · 0.2s · 10ms · 85ms | 2.0s | 34s(*) | 26ms | 28s(*) | |
Errors - when at least 1 argument is incorrect: (uint256,string) ≠ (uint256,bytes)
| Dataset | evmole rs · js · py · go | heimdall | |
| largest1k 24427 functions |
Errors | 14.1% 🥇 3447 |
31.1% 7603 |
| Time | 0.5s · 1.1s · 0.5s · 2.1s | 370s(*) | |
| random50k 1171102 functions |
Errors | 4.8% 🥇 56464 |
19.4% 227077 |
| Time | 13s · 26s · 15s · 44s | 8579s(*) | |
| vyper 21244 functions |
Errors | 48.4% 🥇 10289 |
100.0% 21244 |
| Time | 0.5s · 1.3s · 0.5s · 1.4s | 29s(*) | |
Errors - Results are not equal (treating view and pure as equivalent to nonpayable)
Errors strict - Results are strictly unequal (nonpayable ≠ view). Some ABIs mark pure/view functions as nonpayable, so not all strict errors indicate real issues.
| Dataset | evmole rs · js · py · go | whatsabi | sevm | heimdall | |
| largest1k 24427 functions |
Errors | 0.0% 🥇 0 |
68.1% 16623 |
2.1% 501 |
25.7% 6268 |
| Errors strict | 18.6% 🥇 4549 |
79.4% 19393 |
59.0% 14417 |
54.8% 13386 |
|
| Time | 8.0s · 8.2s · 8.5s · 18s | 2.5s | 27s(*) | 371s(*) | |
| random50k 1160861 functions |
Errors | 0.0% 🥇 44 |
30.2% 351060 |
0.3% 3370 |
11.5% 133471 |
| Errors strict | 6.8% 🥇 78359 |
58.2% 675111 |
55.7% 646831 |
27.6% 320264 |
|
| Time | 157s · 160s · 171s · 354s | 51s | 2261s(*) | 8334s(*) | |
| vyper 21166 functions |
Errors | 0.5% 🥇 110 |
100.0% 21166 |
76.3% 16150 |
100.0% 21166 |
| Errors strict | 4.0% 🥇 850 |
100.0% 21166 |
90.2% 19092 |
100.0% 21166 |
|
| Time | 8.4s · 7.6s · 8.4s · 17s | 1.8s | 35s(*) | 29s(*) | |
False Negatives - Valid blocks possibly incorrectly marked unreachable by CFG analysis. Lower count usually indicates better precision.
| evmole | ethersolve | evm-cfg | sevm | heimdall-rs | evm-cfg-builder | |
| Basic Blocks | 97.0% 🥇 661959 |
93.8% 640383 |
63.0% 430011 |
41.4% 282599 |
31.9% 217924 |
21.7% 148166 |
| False Negatives | 3.0% 🥇 20482 |
6.2% 42058 |
37.0% 252430 |
58.6% 399842 |
68.1% 464517 |
78.3% 534275 |
| Time | 19s | 643s | 49s | 28s | 206s | 158s |
dataset largest1k, 1000 contracts, 682,441 blocks
See benchmark/README.md for the methodology and commands to reproduce these results
versions: evmole v0.8.2; whatsabi v0.25.0; sevm v0.7.4; evm-hound-rs v0.1.4; heimdall-rs v0.8.6
(*): sevm and heimdall-rs are full decompilers, not limited to extracting function selectors
EVMole uses symbolic execution with a custom EVM implementation to trace how CALLDATA flows through the bytecode:
This approach is more accurate than static pattern matching because it follows the actual execution paths the EVM would take, correctly handling complex dispatchers, proxy patterns, and compiler-specific optimizations from both Solidity and Vyper.
- EVMole: function selectors and arguments from bytecode - BlockSplit 2024
- EVMole: function selectors and arguments from bytecode - EthCC 2024
- Reconstructing Control Flow Graphs from EVM Bytecode - ETHTaipei 2025
- Reconstructing Control Flow Graphs from EVM Bytecode: Faster, Better, Stronger - EthCC 2025
MIT