Manual Audit vs Automated Scanner: What Tools Actually Catch
Manual audit vs automated scanner for smart contracts: what Slither and Echidna catch, what only a human auditor finds, and when to use each.
The Same Contract, Two Reviews
Run this staking function through Slither and it will pass clean, no warnings:
function stake(uint256 amount) external {
token.transferFrom(msg.sender, address(this), amount);
stakedAmount[msg.sender] += amount;
stakeTime[msg.sender] = block.timestamp;
}
function calculateReward(address user) public view returns (uint256) {
uint256 duration = block.timestamp - stakeTime[user];
return (stakedAmount[user] * rewardRate * duration) / PRECISION;
}
Nothing here trips a detector. There's no reentrancy, no unchecked call, no obvious overflow. But stake() overwrites stakeTime[msg.sender] on every deposit without paying out or checkpointing the reward that already accrued. A user who stakes 100 tokens, waits 30 days, then "tops up" with 1 wei resets their clock and forfeits nothing they care about — but a user who was supposed to get slashed for early exit just laundered their reward window. Depending on how withdrawal is gated elsewhere in the contract, this is either a minor accounting bug or a direct way to double-dip rewards. A scanner has no model of what "correct" reward accrual looks like for your protocol. A person reading the spec next to the code catches it in about ninety seconds.
That's the whole story of manual audit vs automated scanner for smart contracts, compressed into one function. Tools are pattern matchers. Auditors carry a mental model of what the contract is supposed to do and check the code against that model, not against a signature database.
What Slither Actually Catches
Slither runs static analysis over the AST and control-flow graph, and it's genuinely good at what it targets: reentrancy across public/external functions, unprotected selfdestruct, missing zero-address checks, uninitialized storage pointers, incorrect ERC-20 return-value handling, shadowed state variables, and tx.origin misuse. It's fast — seconds per contract — and it's free. Any team shipping Solidity without running it first is skipping a step that costs nothing.
Take the classic reentrancy pattern:
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount, "insufficient balance");
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "transfer failed");
balances[msg.sender] -= amount;
}
Slither flags this instantly as a checks-effects-interactions violation — external call before state update. This is exactly the bug class detectors were built for: syntactically visible, no business context required, matches a known template from a decade of exploited contracts.
What Echidna Adds
Property-based fuzzing catches a different layer. Where Slither reads code, Echidna executes it — thousands of randomized transaction sequences against invariants you write, like "total supply never exceeds cap" or "sum of balances always equals contract balance." It'll find integer edge cases, unexpected state transitions from weird call orderings, and invariant breaks that only show up after a specific sequence of five or six calls a human wouldn't think to trace by hand. It's excellent at input-space exploration and useless if you never wrote the invariant in the first place — Echidna can't guess what your contract is supposed to guarantee.
What Only a Human Catches
The bugs that cost protocols eight figures are almost never in the reentrancy-detector category anymore. They're in the gap between "code that executes correctly" and "code that does what the business intended":
- Access control that's syntactically fine but logically wrong. A function correctly checks
onlyOwner, but the owner role can be transferred through a separate, unaudited path that bypasses timelock — nothing flags because both functions are individually correct. - Price/oracle assumptions baked into math. A liquidation formula that's arithmetically sound but assumes an oracle updates every block, which breaks the moment that chain has a quiet period.
- Economic incentive misalignment. A fee-on-transfer token interacting with a vault that assumes 1:1 deposit accounting — no revert anywhere, just slow value leakage.
- Governance and upgrade paths. A proxy pattern where the initializer can be called twice because the guard was added to the wrong contract in the inheritance chain.
- Cross-function state assumptions. Function A assumes a state that function B can violate under specific ordering, and neither function alone looks wrong.
None of these produce a detector signature. They require reading the whitepaper or spec, understanding what the protocol promises users, and then asking "under what sequence of legitimate calls does this promise break." That's a reasoning task, not a pattern-match task, and it's why teams that want this depth typically pair automated scanning with a full smart contract audit rather than treating a clean Slither report as sign-off.
Side by Side
| Bug class | Slither | Echidna (fuzzing) | Manual review |
|---|---|---|---|
| Reentrancy, unchecked calls | Catches | Sometimes catches | Catches |
| Integer edge cases, invariant breaks | Misses | Catches (if invariant written) | Catches |
| Access-control logic gaps | Misses | Misses | Catches |
| Business-logic / accounting exploits | Misses | Rarely catches | Catches |
| Oracle & economic assumptions | Misses | Misses | Catches |
| Upgrade/proxy initialization bugs | Sometimes catches | Misses | Catches |
| Speed | Seconds | Minutes to hours | Days |
| Cost | Free / near-free | Low (engineer time to write invariants) | Highest |
Which to Pick When
Run static analysis and fuzzing on every commit — there's no reason not to, and CI-gating a PR on a clean Slither pass is standard practice at this point. But treat that as the floor, not the ceiling. If the contract touches user funds, has an admin key, or handles anything with economic value beyond a testnet toy, budget for manual review before mainnet. A code review and audit pass on top of tooling output is where the actual risk reduction happens, especially for logic-heavy contracts like staking, lending, or anything with a reward curve.
If you're choosing between chains or frameworks and want to know how audit depth differs across ecosystems, our comparisons on FunC vs Tact security auditing and formal verification with Certora vs manual review cover where each approach's blind spots show up on EVM, TON, and Solana specifically. And if you're deciding whether to build an internal review process or bring in outside eyes, in-house review vs third-party audit for TON contracts walks through the tradeoff in more detail.
The Real Workflow
The teams that ship without incidents don't pick one approach — they layer them. Static analysis on every PR, fuzzing on state-changing functions with written invariants, and a manual pass before any mainnet deploy or upgrade. If you're unsure how much of that you actually need for your contract's risk profile, a short strategy consultation is usually faster than guessing, and it's the same triage we'd apply before scoping a bot or Solana sniper contract for adversarial environments where the attack surface is bigger than the code itself.
Tools will keep getting better at pattern matching. They still can't read your spec and ask whether the code actually does what you promised users — that part stays human. If you want that gap closed before mainnet, talk to us about a smart contract audit.
Need a bot like this built?
We design, build and run trading bots on Solana, Hyperliquid and Polymarket.
Start a projectMore from the blog
Public vs Paid RPC: How to Run a Fair Benchmark
A fair comparison of public and paid RPC endpoints must account for quotas, workload, freshness and support guarantees.
Read articleRPC Benchmark Percentiles Explained: p50, p95 and p99
Why averages hide the latency spikes that break trading bots, wallets and production blockchain applications.
Read articleHyperliquid REST vs WebSocket Benchmark: What to Measure
A benchmark plan for Hyperliquid market data that separates request latency from streaming freshness and recovery.
Read article