Cairo smart contract parser with assembler-style GOT/PLT symbol resolution
Project description
Cairo Parser Plugin
Assembler-style Cairo smart contract parser with GOT/PLT symbol resolution.
Overview
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)
Key Features
- ✅ 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
Usage
As Library
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}")
As CLI
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.json
Fail on missing imports (no stubbing):
python -m cairo_parser contracts/ --no-stub
Control 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
Via Raptor
# 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.json
How It Works
Assembler-Style Three-Pass Parsing
Input: 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
Module Path Resolution
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.
Test File Exclusion
Parser automatically skips:
- Files matching:
test_*.cairo,*_test.cairo,tests.cairo - Files in directories:
tests/,test/
Data Structures
ContractInfo
@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]
FunctionInfo
@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
ImportInfo
@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
Output Formats
Summary (Default)
============================================================
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
JSON
{
"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", ...]
}
}
Cairo Language Support
Supported Constructs
- 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 Version Support
- Cairo 1.0+: ✓ Full support
- Cairo 0: ✓ Basic support (regex-based parsing)
Typical Results
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)
Advanced Usage
Disable Stubbing
# Fail on any unresolved imports
contracts = parser.parse_directories([Path('src/')], stub_missing=False)
Single File Parsing (Legacy)
# Parse one file at a time (old behavior, not recommended)
contracts = parser.parse_file(Path('contract.cairo'))
Access Stub Information
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}")
Control Flow and Dataflow Analysis
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'
)
Integration with Analysis Tools
# 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)")
Key Advantages
- ✅ 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
Best Practices
- 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
Files
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
Requirements
- Python 3.8+
- Optional: pyyaml (for YAML output)
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cairo_parser-0.2.0.tar.gz.
File metadata
- Download URL: cairo_parser-0.2.0.tar.gz
- Upload date:
- Size: 47.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
49a33cfc98ddd0ac37cc1fafe0af35a36a8157feb05de0746bf3bc3c575bf8e7
|
|
| MD5 |
46a46975bac3d5b2cf03f56128e26005
|
|
| BLAKE2b-256 |
ff3527acb28c237b5a85f21b03baad4ad9f89b69f4646b62972630f954bcfa96
|
File details
Details for the file cairo_parser-0.2.0-py3-none-any.whl.
File metadata
- Download URL: cairo_parser-0.2.0-py3-none-any.whl
- Upload date:
- Size: 46.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0f6e2019134192e6d96f00bfb1b7129ae32dc07349632d9357a42490a46791e5
|
|
| MD5 |
68d53ef9b41be220b65ff81249c47342
|
|
| BLAKE2b-256 |
a0a902ac1dd18b12faefc1889757c0c8441abebfcd7586e2903b0dd5cb63a8d3
|