Every week we scan a few hundred Solidity contracts pushed by indie devs and small teams. Most are not exotic protocols — they are vaults, NFTs, staking flows, ERC-20s, governance modules. And yet, the same ten vulnerability classes show up again and again. Here they are, ranked roughly by how often we catch them in 2026, with a one-paragraph explanation and a fix you can paste into your code today.
This is not a substitute for a real audit. It is the checklist you run before you pay $30k for one.
1. Reentrancy on external calls
The classic. You call msg.sender.call{value: x}("") before zeroing the user's balance. A malicious receiver re-enters your function in its fallback and drains the contract one balance at a time. This bug is older than the DAO hack and we still flag it on roughly 1 in 4 new vaults.
Fix: apply checks-effects-interactions. Update state before the external call. Or wrap the function in OpenZeppelin's ReentrancyGuard.nonReentrant.
function withdraw() external {
uint256 amount = balances[msg.sender];
require(amount > 0);
balances[msg.sender] = 0; // ✓ state update FIRST
(bool ok, ) = msg.sender.call{value: amount}("");
require(ok, "send failed");
}
2. Unchecked ERC-20 return values
Some tokens (USDT being the most famous) do not revert on a failed transfer — they return false. If you don't check the return value, you keep going as if the transfer succeeded. Free tokens for everyone (your contract pays).
Fix: use SafeERC20.safeTransfer and safeTransferFrom, or require(token.transfer(to, amount), "ERC20 transfer failed").
3. Weak source of randomness
block.timestamp, block.difficulty, blockhash(block.number - 1) — all of these are predictable or manipulable by a miner inside a small window. We see them used in lotteries, NFT mints, and "fair" random rewards.
Fix: Chainlink VRF for unbiasable randomness, or a commit-reveal scheme if you cannot accept Chainlink as a dependency.
4. Owner privileges with no time-lock
A transferOwnership or setAdmin that is callable immediately by owner is a centralization risk that aggregators and bounty hunters will flag. Doubly so if the owner can mint tokens or upgrade the contract.
Fix: put admin actions behind a Timelock contract with a 24–72h delay, and document the privileges in your readme. Bounty hunters will stop flagging it; your users will read it.
5. Integer overflow / underflow on Solidity <0.8
Solidity 0.8 added built-in overflow checks. If your contract still uses pragma ^0.7 and bare + / -, you can wrap silently. We still see this on contracts derived from old templates.
Fix: bump to ^0.8.20. If you need to bump quickly and can't audit the bump, wrap arithmetic in SafeMath.add/sub/mul for the legacy version.
6. Missing zero-address checks
Constructors and setOwner, setRouter, setFeeReceiver functions often accept address(0). The function succeeds, your contract is now permanently locked.
Fix: require(_newAddr != address(0), "zero address"). Yes, one line. Yes, we still have to remind people.
7. Public functions that should be external
When a function is only called from outside the contract, marking it external is cheaper than public because the parameters can be read from calldata. Not a security bug strictly — but Slither flags it, gas reports flag it, and it shows up in your bench-mode score.
Fix: change public to external wherever the function is not called internally.
8. tx.origin used for authentication
tx.origin is the address that started the transaction chain. msg.sender is the immediate caller. If you authenticate with tx.origin, a malicious contract can phish a user into calling its function, which then calls yours — and tx.origin will be the user's address.
Fix: use msg.sender. Reserve tx.origin for non-security uses (logging, analytics).
9. Front-running on price-sensitive ops
Public mempool transactions are visible to MEV bots before they confirm. If you submit a swap, mint, or buy at a fixed price, a bot can sandwich you for free.
Fix: commit-reveal, slippage protection (amountOutMin), private mempool relays (Flashbots Protect on Ethereum, MEV Blocker), or batch auctions.
10. Improper access control on selfdestruct / delegatecall
A reachable selfdestruct or delegatecall to attacker-controlled bytecode is a one-shot drain of the entire contract. The 2017 Parity multisig bug is the canonical example.
Fix: never selfdestruct unless you have a multi-step admin path with a timelock. Never delegatecall to an address that isn't a hardcoded trusted library. Audit each delegatecall site twice.
Run them all in 60 seconds
Each of the bugs above is caught by at least one of Slither, Mythril, or Aderyn. AEDSC bundles all three, normalizes their output, and writes a fix PR for the ~70% of findings where the fix is mechanical. The other 30% — the architectural ones, the economic ones — are why you still need a human audit before mainnet.