How a Cairo contract gets compromised
StarkNet's design assumption is that every account is a contract. There is no EOA (externally owned account) in the EVM sense. Every transaction originates from a smart-contract wallet that validates its own signatures. Account abstraction is not a feature; it is the default.
That design choice moves the audit surface. Every account-abstraction concept in EVM is mandatory in Cairo: signature-verification logic, nonce model, fee-payment flow, validation-vs-execution-phase separation, class-hash upgrade authority. Every Cairo audit has to address them.
The audit methodology applies to Cairo the same way it applies to EVM. Read it first if you have not.
What's different from EVM
Three differences shape the audit work.
Account abstraction by default. Every account is a contract with its own __validate__ and __execute__ functions. The contract decides what counts as a valid signature, what counts as a valid nonce, and what fee-payment scheme it uses.
Class hashes and upgrades. A contract on StarkNet is a class (the code) deployed at an address. Upgrades replace the class hash at the address. The replacement is a single transaction; whoever can authorize it controls the contract from that block forward.
L1↔L2 message trust. Messages can flow between L1 (Ethereum) and L2 (StarkNet). L2 contracts can call L1 contracts and vice versa. The trust model on each side is the validity proof; the boundary code is where most cross-layer bugs live.
What we look at first
The validation phase vs the execution phase. Cairo separates __validate__ (where signature/nonce checks happen) from __execute__ (where the call body runs). The validation phase has constrained access to storage to prevent DoS attacks against sequencers. We audit the boundary: what can validate, what can execute, what state changes are visible to each.
The class-hash upgrade authority. Who can call replace_class_syscall on this contract, on what time-lock, with what governance. Same question we ask on EVM upgradeable contracts; different mechanism.
The fee-payment flow. STRK or ETH is paid for execution. The contract or paymaster owes the fee. A misconfigured paymaster can be drained by a malicious validation phase.
The L1↔L2 message handlers. Functions annotated as L1 handlers run when L1 sends a message. Their access control is implicit: only the StarkNet messaging contract on L1 can trigger them. The audit-time check is whether the handler validates the L1 sender address.
The pattern catalog
Validation-phase abuse. The validation phase is rate-limited; a contract that does more than minimal signature verification in __validate__ exposes the sequencer to DoS. A contract that does too little in __validate__ accepts unauthorized executions. We audit the validation-vs-execution boundary on every account.
Felt overflow and underflow. Cairo's primitive type is felt252, an element of a finite field of prime size. Arithmetic on felt252 wraps around at the prime, which is not a power of two. Computations that assume two's-complement behavior, or that compare felts as if they were signed integers, are exposed. The Cairo 1 / 2 era added explicit signed/unsigned integer types; legacy contracts using raw felts still need the audit.
Cairo 0 vs Cairo 1/2 dialect. Cairo went through a major language transition. Cairo 0 (the original, register-machine-style with Python-flavored hint code) is fundamentally different from Cairo 1/2 (a Rust-flavored language with a real type system, ownership semantics, and traits). Production deployments that started on Cairo 0 have been migrating; the contracts we see in audit today range from Cairo 0 holdovers through partial migrations to all-Cairo-1/2 stacks. The pattern catalog above applies across dialects, but tooling support, library availability, and bug-class density differ. We name the dialect at the start of the engagement.
L1↔L2 bridge boundary. StarkNet's L1↔L2 messaging is the canonical bridge for cross-layer state movement. Bugs in the boundary code fall under the same threat model as any cross-chain bridge; the cross-chain bridge paper covers the surface in detail. The Cairo-side audit checks the L2 handler; the L1-side audit checks the Solidity contract that sends and receives messages. Both sides must agree on the message format, the sender authority, and the replay-protection scheme.
Storage variable hash assumptions. Cairo stores variables at addresses computed from pedersen(name, key). A storage layout migration that changes a variable's name or its key structure shifts the storage location. Unlike EVM's sequential storage, Cairo's storage is content-addressed; the migration risks are different (a renamed variable becomes a dead slot rather than colliding with the next variable).
Class-hash trust. A library_call invokes a class hash directly without instantiating a contract at an address. The caller must verify the class hash is the one it expects. Library calls against attacker-controlled class hashes are equivalent to delegatecall against attacker-controlled implementation.
L1 sender validation. L1 handlers run when L1 sends a message. The handler must verify l1_sender_address is the expected L1 contract. Without the check, any L1 contract can trigger any L2 handler.
Sequencer-only operations. Some StarkNet design patterns rely on the sequencer being the only entity that can call certain functions during specific phases. The current sequencer is centralized; the trust model is "the sequencer behaves correctly." Audit-time question: which operations depend on sequencer trust, and what happens when the sequencer is later decentralized?
Nonce replay across forks. StarkNet's nonce model is per-contract. An account that uses its own nonce scheme (signed messages with embedded nonces, for instance) must include chain-id or equivalent to prevent replay across StarkNet mainnet, testnets, and other chains running compatible code.
Fee abstraction abuse. A paymaster contract that pays fees on behalf of users is a target. A misconfigured paymaster can be drained by a validation phase that consumes its fee budget without doing useful work.
What we audit on a Cairo contract
We audit:
- the validation phase's storage access bounds and computational cost
- the execution phase's signature scheme and nonce model
- the class-hash upgrade authority and time-lock
- the L1↔L2 message handlers for sender validation
- every felt-arithmetic path for overflow assumptions
- every
library_callfor class-hash trust - the paymaster (if present) for drain resistance
- sequencer-trust assumptions explicitly
The deliverable is the audit report described in our methodology.
Tools that earn their place
Caracal is a static analyzer for Cairo. Catches access-control patterns, dead code, and the obvious felt-arithmetic issues.
Amarna is a linter for Cairo. Earlier-generation; useful on legacy Cairo 0 code.
Voyager and the StarkNet block explorers are the canonical sources for verifying deployed class hashes, declared upgrades, and the actual L1↔L2 message traffic for a contract. The audit reviews on-chain state through Voyager (or equivalent explorers) rather than relying on the team's own ABI documentation, which can drift.
Cairo native tooling (scarb test, starknet-py, and the broader Cairo 1/2 testing harnesses) supports unit and integration testing. The ecosystem is younger than Foundry's; testing patterns are less mature.
Formal verification on Cairo is maturing. Certora has been adding Cairo support; the Lean-based proof workflow from earlier Cairo formal-verification research remains an alternative for specific properties. For the highest-stakes deployments, the audit budget should account for one or both, plus manual proof work on the contract-specific invariants the tooling does not yet cover.
What clean means
A clean Cairo audit means the account-abstraction logic, the L1↔L2 message handlers, and the class-hash authority are sound under the threat model the contract states. It does not mean the sequencer will not act adversarially in a way the contract does not currently defend against. The sequencer's decentralization roadmap is still in progress; we name the dependency explicitly.