Auditing smart contracts: reentrancy, invariants, and formal checks
A smart contract is software with the failure mode of a bank vault whose blueprints are public. The code is immutable once deployed, the balances it guards are real, and every attacker on earth can read it at leisure and strike at any hour. Auditing one is less like reviewing a web service and more like checking a parachute: there is no second attempt.
Reentrancy: the classic that still ships
The oldest serious bug in the field is still the most common. A contract that sends funds before it updates its own state hands control to the recipient mid-transaction, and a malicious recipient can call back into the same function before the first call finishes — withdrawing again and again against a balance that has not yet been decremented. The fix is old and reliable: follow the checks-effects-interactions order, mutating all internal state before making any external call, and add a reentrancy guard for anything that touches value. We grep every external call in an audit and trace what state is still stale when control leaves the contract.
State invariants are your real spec
Functions are easy to reason about one at a time and treacherous in combination. What actually protects a protocol is its invariants — the properties that must hold no matter what sequence of calls anyone makes. The sum of user balances equals the contract's token holdings. Total shares never exceed total assets. No account can withdraw more than it deposited. We write these down explicitly, because a function that looks correct in isolation is worthless if some other path lets a user violate the invariant the whole system depends on.
Tests show the contract works on the inputs you imagined. Invariants and proofs constrain the inputs you didn't.— Protocore · Blockchain engineering
Prove what tests can only sample
Unit tests check the cases you thought of, which is exactly the wrong coverage for adversarial code. So we layer two techniques that explore the cases you didn't. Fuzzing throws millions of randomized call sequences at the contract and asserts the invariants survive every one. Formal verification goes further, mathematically proving a property holds for all possible inputs rather than sampling them — expensive, slow, and worth it for the handful of invariants whose violation drains the treasury. Between the two, the goal is the same: turn 'we couldn't break it' into 'it cannot be broken in this specific way.'
An audit is not a stamp; it is a documented argument that the contract does what it claims and nothing else. We deliver the invariants we checked, the proofs and fuzz campaigns behind them, and an honest map of the residual risk — the assumptions about oracles, governance, and upgradeability that no amount of Solidity review can discharge. Immutable code rewards paranoia. The time to find the bug is while you can still redeploy, not after the funds are gone and the transaction is final.
Have a system to build?
Tell us the problem. We'll come back with an architecture and a plan.
Start a project