What a pre-audit contract teardown is (and isn't)

A teardown is a hard, line-by-line read of a smart contract before mainnet. It is not a formal audit, and it is most useful when it doesn't pretend to be one.

Rump Labs4 min read

On this page
  1. The gap it fills
  2. What a teardown is
  3. What it isn't
  4. What gets read
  5. Severity, in plain terms
  6. Tests that encode the claims
  7. How to prepare for one
  8. After the teardown

The gap it fills

A smart contract is public, immutable once deployed (or upgradeable only through mechanisms that are themselves attack surface), and often holds money. Mistakes are found by whoever looks hardest, and on mainnet that is frequently an attacker.

Formal audits exist for this, and they matter. They are also scheduled weeks ahead and priced for finished code. Teams tend to arrive with contracts that still have obvious problems, and spend audit time on findings a careful read would have caught earlier. A pre-audit teardown is that careful read.

What a teardown is

A line-by-line review of a contract by an engineer whose job, for that week, is to break it. The output is written:

  • Findings graded by severity, each with the affected lines, what goes wrong, and a suggested fix.
  • The assumptions the code relies on: who is trusted, which inputs are assumed sane, which external contracts are assumed to behave.
  • The trust model: which roles can do what, and what happens if a privileged key is lost or compromised.
  • What an attacker can do, written as concrete sequences of calls rather than categories.
  • Gas notes on the hot paths.

What it isn't

It is not a formal audit, and it does not come with a certificate or a badge. It is not formal verification; nothing is mathematically proven about the contract. It is not a guarantee that the code is safe.

Saying so plainly is part of the job. A teardown that dresses itself up as an audit gives a team false confidence at exactly the moment they need an accurate picture. Its value is in finding the problems early and cheaply, so the formal audit can spend its time on the subtle ones.

What gets read

Every line, with particular attention to the classes of bug that recur:

  • Access control: functions that should be restricted and are not, initialisers that can be called twice, roles that can grant themselves more power.
  • Reentrancy: external calls made before state is updated, including through token callbacks.
  • External calls and return values: unchecked low-level calls, tokens that do not return booleans, fee-on-transfer and rebasing tokens.
  • Price and oracle manipulation: values read from a pool that a flash loan can move within one transaction.
  • Arithmetic and rounding: precision loss, rounding in the user's favour, division before multiplication.
  • Signatures: replay across chains or contracts, missing nonces, malleability.
  • Upgradeability: storage layout collisions, unprotected upgrade paths, implementation contracts left uninitialised.
  • Denial of service: unbounded loops, and pull-versus-push payment patterns that let one bad recipient block everyone.
  • Ordering: what a front-runner or a block builder gains by seeing a transaction first.

Severity, in plain terms

SeverityWhat it means
CriticalFunds can be stolen or permanently locked, by anyone, now
HighLoss of funds or control under realistic conditions
MediumLoss under specific conditions, or broken core behaviour
LowUnexpected behaviour with limited impact
InformationalClarity, style or gas, with no direct security impact

Tests that encode the claims

A finding is stronger when it comes with a test that fails. The most useful tests in a teardown are invariants: properties that must hold after any sequence of calls. Foundry's invariant testing calls the contract's functions in random sequences with random inputs and checks the property after each one.

Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {Test} from "forge-std/Test.sol";
import {Vault} from "../src/Vault.sol";

contract VaultInvariants is Test {
    Vault vault;

    function setUp() public {
        vault = new Vault();
        targetContract(address(vault));
    }

    /// Whatever anyone does, the vault can pay everyone back.
    function invariant_solvent() public view {
        assertGe(address(vault).balance, vault.totalDeposits());
    }
}

An invariant written down is also documentation. It says what the contract promises, in a form that runs on every future change.

How to prepare for one

  • Freeze the code. Review a specific commit; changes during the read make findings unreliable.
  • Write down the intent. What should each function do, who may call it, and what must never happen.
  • List the dependencies: external contracts, oracles, tokens, and the assumptions you make about each.
  • Include the deployment plan: constructor arguments, admin keys, upgrade mechanism, the chain it will live on.
  • Share the tests you have, even partial ones. They show what you already believe the code does.

After the teardown

Findings usually turn into a fix sprint: the critical and high issues fixed, tests added that would have caught them, and the report updated. Then the formal audit, with code that is cleaner, a written trust model the auditors can start from, and fewer basic findings to spend their time on.

This is the teardown track of our Contract Sprint: up to 500 lines, findings graded by severity, a written report on assumptions, trust model and attacker capabilities, and a walkthrough call. We state in writing that it is pre-audit work.

Blockchain

Next step

Tell us what
you're building.

Thirty minutes with an engineer, free. You'll hear what it takes, what it costs, and whether we're the right people to do it.