Assembler-style Cairo smart contract parser with GOT/PLT symbol resolution.
Cairo Parser uses a three-pass assembler-style approach to parse Cairo contracts:
- Pass 1 (Build Symbol Table): Parse all
.cairofiles and build global symbol table (like GOT - Global Offset Table) - Pass 2 (Link Symbols): Resolve imports by looking up the symbol table (like assembler linking)
- Pass 3 (Create Stubs): Create stubs only for truly external dependencies (like PLT - Procedure Linkage Table)
- ✅ No compiler required - uses regex pattern matching
- ✅ Multi-directory support - parse multiple source directories at once
- ✅ Assembler-style linking - resolves local imports automatically
- ✅ Smart stubbing - stubs only external dependencies (stdlib, runtime)
- ✅ Cairo 0 & 1 support - handles both syntax versions
- ✅ Test file exclusion - automatically skips test files
- ✅ Control Flow Analysis - builds control flow graphs (CFG) for functions
- ✅ Dataflow Analysis - tracks def-use chains, storage access, and external calls
- ✅ Security Analysis - detects uninitialized variables and unused definitions
from pathlib import Path
from parser import CairoParser
# Initialize parser
parser = CairoParser()
# Parse multiple directories (assembler-style)
contracts = parser.parse_directories([
Path('contracts/'),
Path('lib/'),
Path('deps/')
])
for name, contract in contracts.items():
print(f"Contract: {name}")
print(f" Functions: {len(contract.functions)}")
print(f" Storage: {len(contract.storage_vars)}")
print(f" Events: {len(contract.events)}")
# Check import resolution
for imp in contract.imports:
status = "✓ Resolved" if imp.resolved else "✗ Stubbed"
print(f" Import [{status}]: {imp.module_path}")
# Get stub report
stub_report = parser.get_stub_report()
print(f"\nTotal stubs created: {stub_report['total_stubs']}")
print(f"Total resolved: {stub_report['total_resolved']}")
for module in stub_report['stubbed_modules']:
print(f" - {module}")Parse single directory:
python -m cairo_parser contracts/Parse multiple directories (assembler-style):
python -m cairo_parser contracts/ lib/ deps/JSON output with stub report:
python -m cairo_parser contracts/ --format json --stub-report -o output.jsonFail on missing imports (no stubbing):
python -m cairo_parser contracts/ --no-stubControl Flow and Dataflow Analysis:
# Perform analysis and save results
python -m cairo_parser contracts/ --analyze --analysis-output analysis.json
# Show analysis warnings in summary output
python -m cairo_parser contracts/ --analyze --show-warnings
# Full analysis with both parser and analysis output
python -m cairo_parser contracts/ --analyze \
--format json -o contracts.json \
--analysis-output analysis.json \
--analysis-format yaml# Parse Cairo contracts
raptor parse-cairo src/
# Multiple directories
raptor parse-cairo contracts/ lib/ --stub-report
# JSON output
raptor parse-cairo src/ --format json -o contracts.jsonInput: contracts/ lib/ deps/
Pass 1: Parse all files → Build Symbol Table (GOT)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
contracts/positions.cairo → positions::Positions
lib/components/upgradeable.cairo → components::upgradeable
deps/math/delta.cairo → math::delta
Symbol Table: 659 symbols registered
Pass 2: Resolve imports from Symbol Table
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
use crate::components::upgradeable → ✓ Found in symbol table
use crate::math::delta → ✓ Found in symbol table
use core::array → ✗ Not in symbol table
Resolved: 42 imports
Pass 3: Stub external dependencies (PLT)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
core::array → Create stub (Cairo stdlib)
starknet::storage → Create stub (Starknet runtime)
Stubbed: 16 external modules
Result: All local imports resolved, only external deps stubbed
Files are registered using their path relative to src/:
starknet-contracts/src/components/upgradeable.cairo
└─→ components::upgradeable
starknet-contracts/src/math/delta.cairo
└─→ math::delta
Imports like use crate::components::upgradeable resolve to the symbol table entry for components::upgradeable.
Parser automatically skips:
- Files matching:
test_*.cairo,*_test.cairo,tests.cairo - Files in directories:
tests/,test/
@dataclass
class ContractInfo:
name: str
file_path: str
contract_type: str # 'contract', 'interface', 'module', 'stub'
functions: List[FunctionInfo]
storage_vars: List[StorageVarInfo]
events: List[EventInfo]
imports: List[ImportInfo]
# Stubbing metadata
stub_modules: Dict[str, ContractInfo]
unresolved_calls: Set[str]
unresolved_types: Set[str]
# Parsing status
parse_errors: List[str]
parse_warnings: List[str]@dataclass
class FunctionInfo:
name: str
visibility: str # 'external', 'view', 'internal'
parameters: List[Dict[str, str]]
returns: List[Dict[str, str]]
decorators: List[str]
line: int
is_stub: bool # True if from stubbed module@dataclass
class ImportInfo:
module_path: str
symbols: List[str] # Imported symbols
line: int
resolved: bool # Whether import was found in symbol table
stub_created: bool # Whether stub was created============================================================
Cairo Parser Results
============================================================
Total Files: 51
Total Contracts: 11
CONTRACT: Positions
File: starknet-contracts/src/positions.cairo
Functions (48):
- constructor (internal)
- mint (internal)
- withdraw (internal)
Storage Variables (6):
- core: ICoreDispatcher
- nft: IOwnedNFTDispatcher
Imports (26):
[✓] crate::components::upgradeable
[✓] crate::math::delta
[✗ STUBBED] core::array
[✗ STUBBED] starknet::storage
Stub Report
============================================================
Total Stubs: 16
Total Resolved: 42
Stubbed Modules:
- core::array
- core::cmp
- starknet::storage
- starknet
{
"metadata": {
"total_files": 51,
"total_contracts": 11,
"stubbing_enabled": true
},
"contracts": {
"Positions": {
"name": "Positions",
"file_path": "starknet-contracts/src/positions.cairo",
"contract_type": "contract",
"functions": [...],
"imports": [
{
"module_path": "crate::components::upgradeable",
"resolved": true,
"stub_created": false
},
{
"module_path": "core::array",
"resolved": false,
"stub_created": true
}
]
}
},
"stub_report": {
"total_stubs": 16,
"total_resolved": 42,
"total_symbols": 659,
"stubbed_modules": ["core::array", "starknet::storage", ...]
}
}- Contracts:
#[starknet::contract] mod ContractName - Interfaces:
#[starknet::interface] trait IContract - Components:
#[starknet::component] pub mod Component - Functions:
fn function_name(params) -> ReturnType - Storage:
#[storage] struct Storage - Events:
#[event] enum/struct EventName - Imports:
use module::path::{Symbol1, Symbol2};
- Cairo 1.0+: ✓ Full support
- Cairo 0: ✓ Basic support (regex-based parsing)
Parsing a real-world Cairo project:
[Pass 1/3] Parsing 51 files from 1 directory...
[Pass 1/3] Symbol table built: 659 symbols
[Pass 2/3] Resolving imports from symbol table...
[Pass 3/3] Creating stubs for external dependencies...
[Done] Resolved: 42, Stubbed: 16
Analysis:
- 72% resolution rate - 42 out of 58 total imports resolved locally
- Only 16 stubs - Just external dependencies (Cairo stdlib, Starknet runtime)
- 659 symbols - All contracts, modules, functions registered
Stubbed modules (expected):
core::array,core::cmp,core::traits(Cairo standard library)starknet::storage,starknet::get_contract_address(Starknet runtime)super(relative imports)
# Fail on any unresolved imports
contracts = parser.parse_directories([Path('src/')], stub_missing=False)# Parse one file at a time (old behavior, not recommended)
contracts = parser.parse_file(Path('contract.cairo'))contracts = parser.parse_directories([Path('src/')])
for name, contract in contracts.items():
# Check if contract has stubbed dependencies
if contract.stub_modules:
print(f"{name} has {len(contract.stub_modules)} stubbed dependencies")
# Check individual imports
for imp in contract.imports:
if not imp.resolved:
print(f"Stubbed: {imp.module_path}")The parser includes a powerful static analysis engine that performs:
- Control Flow Graph (CFG) construction
- Def-Use chain analysis
- Storage access tracking
- External call detection
- Uninitialized variable detection
- Dead code detection
Basic Analysis:
from cairo_parser import CairoParser, CairoAnalyzer
# Parse contracts
parser = CairoParser()
contracts = parser.parse_directories([Path('contracts/')])
# Analyze contracts
analyzer = CairoAnalyzer()
results = analyzer.analyze_contracts(contracts)
# Get summary statistics
summary = analyzer.get_summary_stats(results)
print(f"Analyzed {summary['functions_with_body']} functions")
print(f"Found {summary['total_warnings']} warnings")
print(f"Storage reads: {summary['total_storage_reads']}")
print(f"Storage writes: {summary['total_storage_writes']}")
print(f"External calls: {summary['total_external_calls']}")Detailed Function Analysis:
# Analyze specific function
for result in results:
for func in result.functions:
if func.has_body:
# Access CFG
cfg = func.cfg
print(f"Function {func.function_name}:")
print(f" CFG nodes: {len(cfg['nodes'])}")
print(f" Entry: {cfg['entry_node']}")
print(f" Exits: {cfg['exit_nodes']}")
# Access dataflow results
dataflow = func.dataflow
print(f" Def-use chains: {len(dataflow['def_use_chains'])}")
print(f" Storage accesses: {len(dataflow['storage_accesses'])}")
print(f" External calls: {len(dataflow['external_calls'])}")
# Check warnings
for warning in func.warnings:
print(f" Warning: {warning['message']}")CFG Visualization:
from cairo_parser.analysis import CFGBuilder, StatementParser
# Parse function body
stmt_parser = StatementParser()
statements = stmt_parser.parse(function.body_text, function.body_start_line)
# Build CFG
cfg_builder = CFGBuilder()
cfg = cfg_builder.build(function.name, statements)
# Access CFG nodes and edges
for node in cfg.nodes:
print(f"Node {node.id} ({node.node_type.value}):")
if node.statement:
print(f" Statement: {node.statement.raw_text}")
print(f" Successors: {node.successors}")
print(f" Predecessors: {node.predecessors}")
# Compute dominators
dominators = cfg_builder.compute_dominators()
for node_id, dom_set in dominators.items():
print(f"Node {node_id} dominated by: {dom_set}")
# Find all execution paths
paths = cfg_builder.find_all_paths(max_paths=100)
print(f"Found {len(paths)} execution paths")Dataflow Analysis:
from cairo_parser.analysis import DataflowAnalyzer
# Create dataflow analyzer
dataflow_analyzer = DataflowAnalyzer(cfg)
# Analyze def-use chains
def_use_chains = dataflow_analyzer.analyze_def_use_chains()
for chain in def_use_chains:
var = chain['variable']
defs = chain['definitions']
uses = chain['uses']
print(f"Variable '{var}': defined at nodes {defs}, used at nodes {uses}")
# Track storage access
storage_accesses = dataflow_analyzer.analyze_storage_access()
for access in storage_accesses:
print(f"{access['access_type'].upper()}: {access['storage_var']} at line {access['line']}")
# Find external calls
external_calls = dataflow_analyzer.analyze_external_calls()
for call in external_calls:
print(f"External call: {call['function_name']}({', '.join(call['arguments'])})")
# Detect potential issues
uninit_vars = dataflow_analyzer.find_uninitialized_variables()
for warning in uninit_vars:
print(f"Warning: {warning['message']} at line {warning['line']}")
unused_defs = dataflow_analyzer.find_unused_definitions()
for warning in unused_defs:
print(f"Warning: {warning['message']}")Saving Analysis Results:
from cairo_parser.analysis.serialization import save_analysis
# Save as JSON
save_analysis(
[r.to_dict() for r in results],
Path('analysis.json'),
format='json',
pretty=True
)
# Save as YAML
save_analysis(
[r.to_dict() for r in results],
Path('analysis.yaml'),
format='yaml'
)# Parse with stubbing
contracts = parser.parse_directories([Path('contracts/')])
# Run analysis (even with stubs)
analyzer = CairoAnalyzer()
for name, contract in contracts.items():
if not contract.is_stub:
result = analyzer.analyze_contract(contract)
for func_result in result.functions:
if func_result.has_body:
# Analyze real functions
print(f"Analyzing {func_result.function_name}")
# ... use CFG and dataflow results
else:
# Skip stubbed or bodyless functions
print(f"Skipping {func_result.function_name} (no body)")- ✅ No external dependencies - just Python stdlib + regex
- ✅ Assembler-style linking - resolves symbols across all files first
- ✅ High resolution rate - typically 70%+ local imports resolved
- ✅ Smart stubbing - only external dependencies stubbed
- ✅ Multi-directory support - parse entire dependency trees
- ✅ Fast - no compiler overhead
- Parse from project root: Include all source directories
- Use multiple directories:
python -m cairo_parser contracts/ lib/ deps/ - Check stub report: Use
--stub-reportto see what's external - Expect external stubs: Cairo stdlib and Starknet runtime will always be stubbed
parser.py- Core Cairo parser with GOT/PLT symbol resolutionanalysis/- Control flow and dataflow analysis modulesanalyzer.py- Main analysis orchestratorcfg.py- Control flow graph builderdataflow.py- Dataflow analysis (def-use chains, storage tracking)statements.py- Statement parser for function bodiesserialization.py- JSON/YAML output support
install.py- Plugin installation and registration__init__.py- Package initialization__main__.py- CLI interface
- Python 3.8+
- Optional: pyyaml (for YAML output)