Try it Live Run Bytecode examples in the interactive playground
bytecode_to_abi(bytecode_t bytecode) const char*TypeScript implementation only. ABI reverse engineering not available in C. // TypeScript implementation only
// ABI reverse engineering not available in C
How It Works
toAbi() analyzes bytecode to extract interface information by:
Function Selector Detection - Identifies function dispatch jump tables
Payability Analysis - Detects CALLVALUE checks for non-payable modifiers
State Mutability - Infers view/pure based on SLOAD/SSTORE presence
Event Extraction - Finds PUSH32 + LOG patterns for event signatures
Proxy Detection - Identifies proxy patterns (EIP-1967, etc.)
Detection Patterns
Function Selectors
Solidity function dispatchers follow predictable patterns:
DUP1 PUSH4 0x2E64CEC1 EQ PUSH2 0x0037 JUMPI
└─────┬─────┘ └────┬────┘
Selector (4 bytes) Jump target
The analyzer walks the jump table to extract all function selectors and their entry points.
Payability
Non-payable functions have guard checks:
JUMPDEST CALLVALUE DUP1 ISZERO ...
└──────────────┬──────────┘
Revert if msg.value > 0
Functions without this pattern are marked as payable.
State Mutability
Inferred from opcode usage:
Opcodes Mutability Meaning SSTORE present nonpayableModifies state Only SLOAD viewReads state No SLOAD/SSTORE pure (tentative)No state access Has CALLVALUE checks payableAccepts ETH
State mutability inference has limitations with dynamic jumps. Functions may be incorrectly classified if control flow cannot be fully analyzed.
Events
Event signatures detected from LOG patterns:
PUSH32 0x... <topic hash>
LOG1/LOG2/LOG3/LOG4
The PUSH32 value before a LOG instruction is extracted as an event topic hash.
Return Type
import type { brand } from 'tevm/brand' ;
type BrandedAbi = ReadonlyArray < ABIItem > & { readonly [ brand ] : "Abi" };
type ABIItem = ABIFunction | ABIEvent ;
interface ABIFunction {
type : "function" ;
selector : string ; // 4-byte function selector (e.g., "0x70a08231")
stateMutability : StateMutability ;
payable : boolean ;
inputs ?: [{ type : "bytes" ; name : "" }]; // Generic if detected
outputs ?: [{ type : "bytes" ; name : "" }]; // Generic if detected
}
interface ABIEvent {
type : "event" ;
hash : string ; // 32-byte topic hash (e.g., "0xddf2...")
}
type StateMutability = "pure" | "view" | "nonpayable" | "payable" ;
Reconstructed ABIs use generic bytes types since exact parameter structures cannot be determined from bytecode alone. Parameter names are always empty strings.
Usage Patterns
Interact with Unverified Contracts
const bytecode = await provider . getCode ( mysteryContract );
const code = Bytecode ( bytecode );
const abi = code . toAbi ();
// Find all view functions
const viewFunctions = abi . filter (
item => item . type === 'function' && item . stateMutability === 'view'
);
console . log ( `Found ${ viewFunctions . length } view functions:` );
viewFunctions . forEach ( fn => {
console . log ( ` ${ fn . selector } ` );
});
// Try calling a function by selector
const result = await provider . call ({
to: mysteryContract ,
data: viewFunctions [ 0 ]. selector + "00" . repeat ( 32 ) // Selector + padding
});
Verify Deployed Contract
// Compare deployed bytecode ABI with expected
const deployed = Bytecode ( await provider . getCode ( address ));
const deployedAbi = deployed . toAbi ();
const expected = Bytecode ( compiledBytecode );
const expectedAbi = expected . toAbi ();
// Check if function selectors match
const deployedSelectors = new Set (
deployedAbi
. filter ( item => item . type === 'function' )
. map ( fn => fn . selector )
);
const expectedSelectors = new Set (
expectedAbi
. filter ( item => item . type === 'function' )
. map ( fn => fn . selector )
);
const matching = [ ... deployedSelectors ]. filter ( s => expectedSelectors . has ( s ));
const missing = [ ... expectedSelectors ]. filter ( s => ! deployedSelectors . has ( s ));
console . log ( `Matching functions: ${ matching . length } ` );
if ( missing . length > 0 ) {
console . warn ( `Missing functions: ${ missing . join ( ', ' ) } ` );
}
Detect Proxy Contracts
const code = Bytecode ( proxyBytecode );
const abi = code . toAbi ();
// Proxy contracts typically have very few functions
// and minimal SSTORE operations
const functionCount = abi . filter ( item => item . type === 'function' ). length ;
if ( functionCount < 5 ) {
console . log ( 'Likely a proxy contract (minimal interface)' );
// Check for proxy-specific patterns
const hasFallback = abi . some (
item => item . type === 'function' && item . selector === '0x00000000'
);
if ( hasFallback ) {
console . log ( 'Has fallback function - typical of proxies' );
}
}
ABI Recovery for Lost Source
// Contract source lost but bytecode deployed
const code = Bytecode ( lostContractBytecode );
const reconstructedAbi = code . toAbi ();
// Save reconstructed ABI
import { writeFileSync } from 'fs' ;
writeFileSync (
'recovered-abi.json' ,
JSON . stringify ( reconstructedAbi , null , 2 )
);
// Map selectors to known function signatures
const knownSignatures : Record < string , string > = {
'0x70a08231' : 'balanceOf(address)' ,
'0xa9059cbb' : 'transfer(address,uint256)' ,
'0x23b872dd' : 'transferFrom(address,address,uint256)' ,
};
reconstructedAbi . forEach ( item => {
if ( item . type === 'function' ) {
const signature = knownSignatures [ item . selector ];
if ( signature ) {
console . log ( ` ${ item . selector } → ${ signature } ` );
} else {
console . log ( ` ${ item . selector } → <unknown>` );
}
}
});
Compare Contract Versions
const v1 = Bytecode ( contractV1Bytecode );
const v2 = Bytecode ( contractV2Bytecode );
const abiV1 = v1 . toAbi ();
const abiV2 = v2 . toAbi ();
// Find added functions
const selectorsV1 = new Set (
abiV1 . filter ( i => i . type === 'function' ). map ( f => f . selector )
);
const selectorsV2 = new Set (
abiV2 . filter ( i => i . type === 'function' ). map ( f => f . selector )
);
const added = [ ... selectorsV2 ]. filter ( s => ! selectorsV1 . has ( s ));
const removed = [ ... selectorsV1 ]. filter ( s => ! selectorsV2 . has ( s ));
console . log ( `Version 2 changes:` );
console . log ( ` Added: ${ added . length } functions` );
console . log ( ` Removed: ${ removed . length } functions` );
added . forEach ( s => console . log ( ` + ${ s } ` ));
removed . forEach ( s => console . log ( ` - ${ s } ` ));
Limitations
ABI extraction from bytecode is heuristic-based and has fundamental limitations: Cannot Determine ❌ Exact parameter types - Only knows function takes inputs, not their structure
❌ Parameter names - No name information in bytecode
❌ Return types - Cannot distinguish between uint256, address, bytes32, etc.
❌ Function names - Only 4-byte selector hash available
❌ Event names - Only 32-byte topic hash available
❌ Struct layouts - Cannot reconstruct complex types
❌ Array dimensions - Cannot determine fixed vs dynamic arrays May Miss ⚠️ Dynamic dispatch functions - Functions with computed jump targets
⚠️ Inline assembly routing - Custom dispatcher patterns
⚠️ Dead code - Unreachable functions may be detected anyway
⚠️ Optimized dispatchers - Non-standard jump table patterns May Misclassify 🟡 State mutability - Limited by static analysis of reachable code
🟡 Payability - Edge cases with unconventional guard patterns
🟡 Proxy detection - May not detect custom proxy implementations Recommendation : Use reconstructed ABIs for discovery and verification. For production interactions, always prefer verified source ABIs when available.
What Works Reliably
✅ Function selector extraction (standard Solidity)
✅ Payability detection (CALLVALUE patterns)
✅ Event topic hashes (PUSH32 + LOG patterns)
✅ Basic state mutability (view vs nonpayable)
✅ Proxy detection (EIP-1967, minimal proxies)
Compiler Compatibility
Works best with standard compiler output:
Compiler Version Compatibility Solidity 0.4.x - 0.8.x ✅ Excellent - Standard jump tables Vyper 0.2.x - 0.3.x ⚠️ Good - Some non-standard patterns Yul Any 🟡 Variable - Depends on optimization Hand-written N/A ❌ Poor - No standard patterns
Solidity produces the most analyzable bytecode due to consistent function dispatcher patterns.
ABI extraction performance:
Bytecode Size Time Functions Detected Small (<5KB) <10ms ~10 functions Medium (10-15KB) ~20ms ~20-30 functions Large (20-24KB) ~50ms ~50+ functions
Results are deterministic and can be cached. Re-running analysis on the same bytecode always produces identical results.
Integration with Abi Module
Reconstructed ABIs are compatible with Abi module methods:
import * as Abi from 'tevm/Abi' ;
const code = Bytecode ( bytecode );
const abi = code . toAbi ();
// Use with Abi module
const encoded = Abi . encodeFunctionData ( abi , functionSelector , args );
const decoded = Abi . decodeFunctionResult ( abi , functionSelector , returnData );
// Format for display
const formatted = Abi . format ( abi );
console . log ( formatted );
Since reconstructed ABIs use generic bytes types, encoding/decoding may require manual type casting or ABI augmentation with known signatures.
Security Considerations
Verification
Never trust reconstructed ABIs for production without verification:
Compare with verified source when available
Test function calls on testnets before mainnet
Validate return data matches expected formats
Check for proxy patterns that might delegate to different implementations
Bytecode Manipulation
Malicious contracts may:
Include fake function selectors in unused code paths
Use dynamic dispatch to hide actual functions
Manipulate jump tables to evade analysis
Include misleading event signatures
Always verify contract source on Etherscan or similar before interacting based on reconstructed ABI.
Privacy
ABI extraction may reveal:
Internal function structure (even if not in public ABI)
Proxy implementation details
Factory patterns and CREATE2 addresses
Compiler version and optimization settings
Advanced Usage
Augment Reconstructed ABI
const abi = code . toAbi ();
// Replace generic types with known signatures
const knownSignatures = [
'function balanceOf(address) view returns (uint256)' ,
'function transfer(address,uint256) returns (bool)' ,
];
const augmentedAbi = abi . map ( item => {
if ( item . type !== 'function' ) return item ;
const known = knownSignatures . find ( sig => {
const selector = Abi . getSelector ( sig );
return selector === item . selector ;
});
return known ? Abi . parseSignature ( known ) : item ;
});
Detect Compiler Version
const code = Bytecode ( bytecode );
const abi = code . toAbi ();
// Heuristics based on patterns
const hasMetadata = code . hasMetadata ();
const functionCount = abi . filter ( i => i . type === 'function' ). length ;
if ( hasMetadata ) {
const metadata = code . stripMetadata ();
// Check metadata for compiler info
console . log ( 'Contract has CBOR metadata (Solidity 0.4.7+)' );
}
// Solidity 0.8+ uses PUSH0 (if available)
const hasPush0 = code . raw (). includes ( 0x5F );
if ( hasPush0 ) {
console . log ( 'Likely Solidity 0.8.0+ (uses PUSH0)' );
}
// Deployment bytecode includes init code + runtime code
const deployBytecode = Bytecode ( deploymentBytecode );
const deployAbi = deployBytecode . toAbi ();
// Extract runtime code
const runtimeCode = deployBytecode . extractRuntime ();
const runtimeAbi = runtimeCode . toAbi ();
console . log ( 'Deployment ABI functions:' , deployAbi . length );
console . log ( 'Runtime ABI functions:' , runtimeAbi . length );
// Init code may have constructor, runtime has contract methods
See Also
Abi - ABI encoding/decoding and formatting
analyze - Complete bytecode analysis
detectFusions - Pattern detection used internally
EventLog - Working with decoded events
References