Skip to main content

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:

  1. Pass 1 (Build Symbol Table): Parse all .cairo files and build global symbol table (like GOT - Global Offset Table)
  2. Pass 2 (Link Symbols): Resolve imports by looking up the symbol table (like assembler linking)
  3. 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

  1. Parse from project root: Include all source directories
  2. Use multiple directories: python -m cairo_parser contracts/ lib/ deps/
  3. Check stub report: Use --stub-report to see what's external
  4. Expect external stubs: Cairo stdlib and Starknet runtime will always be stubbed

Files

  • parser.py - Core Cairo parser with GOT/PLT symbol resolution
  • analysis/ - Control flow and dataflow analysis modules
    • analyzer.py - Main analysis orchestrator
    • cfg.py - Control flow graph builder
    • dataflow.py - Dataflow analysis (def-use chains, storage tracking)
    • statements.py - Statement parser for function bodies
    • serialization.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

GPL-3.0

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cairo_parser-0.2.0.tar.gz (47.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cairo_parser-0.2.0-py3-none-any.whl (46.1 kB view details)

Uploaded Python 3

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

Hashes for cairo_parser-0.2.0.tar.gz
Algorithm Hash digest
SHA256 49a33cfc98ddd0ac37cc1fafe0af35a36a8157feb05de0746bf3bc3c575bf8e7
MD5 46a46975bac3d5b2cf03f56128e26005
BLAKE2b-256 ff3527acb28c237b5a85f21b03baad4ad9f89b69f4646b62972630f954bcfa96

See more details on using hashes here.

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

Hashes for cairo_parser-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0f6e2019134192e6d96f00bfb1b7129ae32dc07349632d9357a42490a46791e5
MD5 68d53ef9b41be220b65ff81249c47342
BLAKE2b-256 a0a902ac1dd18b12faefc1889757c0c8441abebfcd7586e2903b0dd5cb63a8d3

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page