Solidity audit

A Solidity audit that shows you the line and the fix.

Twenty detector classes walk your AST looking for the bugs that are specific to Solidity and the EVM — not generic code smells. Every finding names the pattern, points at the code, cites its SWC entry, and tells you what to change.

Audit my SoliditySee the full checklist

Why Solidity needs its own kind of audit

A Solidity contract audit is not a general code review with different keywords. A web application that crashes gets restarted; a Solidity contract that has a bug is a bug that is deployed, immutable and holding money, with a public source that anyone hostile can read at leisure. Three properties of the language and the EVM make this worse than it sounds, and all three are why general-purpose static analysis does not transfer.

Every external call is a yield point. When your contract calls another address, that address gets to run arbitrary code before returning — including calling back into you. Anywhere your own state is mid-update at that moment is a reentrancy bug. No conventional language has this hazard on an ordinary function call.

Execution is metered and bounded. A loop over an array that grows with your user count is fine in testing and permanently stuck once the array exceeds a block's gas limit. Correct code that becomes uncallable is a denial of service.

Storage is a flat, addressable layout. With delegatecall and proxy patterns, one contract executes against another's storage slots. Get the layouts out of alignment, or let a caller influence the delegate target, and the consequence is total.

The detector classes

The engine ships twenty, grouped below. Each maps to its SWC Registry entry and to the OWASP Smart Contract Top 10, and the report shows the ones that passed as well as the ones that fired.

Security

  • ReentrancyState written after an external call, so a callback can re-enter mid-update.
  • tx.origin authorizationAuth checked against tx.origin, which any intermediary contract can forge.
  • Arbitrary delegatecalldelegatecall to an address a caller can influence, running with your storage.
  • Unchecked low-level callThe return value of call / send discarded, so a failure passes silently.
  • Unprotected selfdestructselfdestruct reachable without an access-control modifier.
  • Weak randomnessRandomness seeded from block or transaction properties a proposer controls.
  • Timestamp dependenceblock.timestamp used in logic that a proposer can nudge.

Access control & trust

  • Missing access controlA privileged function with no modifier guarding it.
  • Centralization riskAn owner able to mint, pause, blacklist or drain — working as written.
  • Missing zero-address checkAn address set without validating it, bricking ownership or transfers.

Correctness & practice

  • Unchecked arithmeticMaths that can wrap: pre-0.8 without SafeMath, or inside unchecked blocks.
  • Floating pragmaA caret pragma, so the deployed bytecode depends on whichever compiler ran.
  • Shadowed variablesA declaration hiding another, where the wrong one gets written.
  • Missing eventsSensitive state changed with nothing emitted, leaving monitoring blind.
  • Unbounded loopA loop over data that grows, until the call cannot fit in a block.

Gas

  • Array length in loopLength re-read from storage every iteration instead of cached.
  • Post-increment in loopsi++ where ++i is cheaper.
  • Custom errorsrequire strings where a custom error would cost less.
  • Public to externalA public function never called internally, cheaper as external.

Two findings, in code

Abstract descriptions of vulnerabilities are hard to map onto your own file, so here are two of the most common as the detector sees them.

Reentrancy: state written after an external call

The single most expensive bug class in the history of the space, and it still ships regularly. The detector flags any state write that happens after an external call within the same function.

// Vulnerable: balance is cleared AFTER the external call.
function withdraw() external {
    uint256 amount = balances[msg.sender];
    (bool ok, ) = msg.sender.call{value: amount}("");
    require(ok, "transfer failed");
    balances[msg.sender] = 0;   // attacker re-enters before this line
}

The caller here is not necessarily a person — it can be a contract whose receive() calls withdraw() again while balances[msg.sender] is still the original amount. The fix is the checks-effects-interactions ordering, and a guard for defence in depth:

// Fixed: checks, effects, then interactions.
function withdraw() external nonReentrant {
    uint256 amount = balances[msg.sender];
    require(amount > 0, "nothing to withdraw");
    balances[msg.sender] = 0;   // effect first
    (bool ok, ) = msg.sender.call{value: amount}("");
    require(ok, "transfer failed");
}

The long version, including the cross-function and read-only variants that catch teams who thought they had fixed it, is in the reentrancy guide.

Authorization via tx.origin

tx.origin is the account that started the transaction; msg.sender is whoever called you directly. Using the former for auth means any contract the owner is tricked into calling can turn around and call you with the owner's origin intact.

// Vulnerable: any contract the owner calls can forge this check.
require(tx.origin == owner, "not owner");

// Fixed: msg.sender is the immediate caller and cannot be spoofed.
require(msg.sender == owner, "not owner");

Getting your Solidity audited

Three inputs, whichever suits where you are: paste the source directly, upload a .sol file or a project .zip with imports intact, or give a verified contract address on any supported chain and the source is pulled from the explorer. Pre-deployment, pasting is usually easiest; post-deployment, the address route also gets you proxy resolution.

Compiler version is taken from the verified metadata rather than assumed, because several findings depend on it — flagging unprotected arithmetic on a 0.8.20 contract with built-in overflow checks would be noise, and missing it on a 0.7 contract without SafeMath would be a real bug shipped.

Testnet address scans are free and unlimited, which makes the fix-and-rescan loop cost nothing while your code is still moving. See the free audit page for what that tier covers.

FAQ

Frequently asked questions

What is a Solidity audit?

A Solidity audit is a security review of contract source code written in Solidity, as opposed to a review of bytecode or of a protocol design. It looks for language- and EVM-specific failure modes — reentrancy through external calls, storage collisions in proxies, arithmetic that wraps, visibility and modifier mistakes — that do not exist in ordinary application code.

Which Solidity versions do you support?

The parser handles the 0.4 through 0.8 line. Version matters to the findings themselves: below 0.8 arithmetic wraps silently unless SafeMath is used, so the analysis flags unprotected maths there, while on 0.8 and above it flags unchecked blocks instead. The compiler version is read from the verified source rather than guessed.

Can you audit a multi-file project with imports?

Yes. Upload a .zip of the project and the imports are resolved across files, or pull a verified address and the flattened multi-file source comes straight from the explorer, with each file kept separate in the report so findings point at the right place.

Do you audit proxies and upgradeable contracts?

Yes. When a verified address turns out to be a proxy, the implementation is resolved and both are audited, labelled separately in the report. Auditing a proxy alone is close to meaningless — almost all the logic your users touch lives behind it.

Do you check gas as well as security?

Yes, as a separate finding class so it never dilutes the security list. The gas detectors flag array length read inside a loop condition, post-increment in loops, require strings that should be custom errors, and public functions that are never called internally and could be external.

What about false positives?

Every finding carries a confidence level alongside its severity, and the AI pass cross-checks the static results to remove duplicates and obvious misfires. Some findings are still contextual — a centralization finding on a contract that is deliberately admin-controlled is accurate but may be intended. The report tells you what was found; deciding what is acceptable is yours.

Point it at your Solidity

Paste the source, upload the project, or give a verified address. The findings come back with line numbers.

Start your auditSee pricing