How to Read Smart Contract Code Before You Invest: A Practical 2025 Due-Diligence Playbook
If code is law, then reading the law is table-stakes. This guide gives you a systematic way to inspect Solidity, proxies, roles, taxes, mints, upgrade paths, vault math, and DeFi integrations even if you’re not yet a full-time auditor. It includes a step-by-step workflow, annotated code snippets, visual diagrams, red-flag patterns, and a printable checklist you can reuse for any token or protocol.
- Verify the exact deployed bytecode and source with a trusted explorer (Etherscan/Basescan) or Sourcify. If code isn’t verified, treat it as hostile until proven safe.
- Follow a five-layer workflow: (1) Identify & verify, (2) Architecture & proxies, (3) Roles & permissions, (4) Token economics & dangerous hooks, (5) External calls & failure modes.
- Print the checklist in this article. If any of: upgradeable proxy without timelock, unrestricted mint/burn, blacklist/whitelist gates, 100% variable tax, or unguarded
delegatecallwalk away.
1) Mindset: what “reading code” really means for investors
You’re not trying to become an auditor overnight. You’re trying to catch the 80% of traps that show up in the first 15–30 minutes of structured review. Think like a firefighter doing a safety sweep: identify fuel sources (mint/burn/tax), ignition points (external calls that can be manipulated), and locked exits (blacklists, honeypots, upgrade backdoors).
- Assume incentives: If a single EOA can upgrade logic, toggle trading, or mint supply, your investment is permissioned at their mercy.
- Assume complexity = risk: Every module (vault, farm, router, bridge) is another place to hide foot-guns.
- Prove safety, don’t assume it: A PDF “audit” on a random server means nothing; verify on the auditor’s own domain.
2) A Five-Layer Workflow for Code Due Diligence
Five-Layer Investor Workflow
┌─────────┐ ┌─────────────┐ ┌───────────────┐ ┌───────────────┐ ┌──────────────────┐
│ Layer 1 │→→│ Layer 2 │→→│ Layer 3 │→→│ Layer 4 │→→│ Layer 5 │
│ Verify │ │ Architecture │ │ Roles/Perms │ │ Token Economics│ │ External & Fails │
└─────────┘ └─────────────┘ └───────────────┘ └───────────────┘ └──────────────────┘
Stop at first failure. Proceed only if the previous layer is clean.
3) Layer 1 — Identify & verify: source, bytecode, constructor args
3.1 Verify you’re looking at the right contract
- Get the address from the project’s official website or pinned X/Twitter post. Cross-check on a major explorer (Etherscan, Basescan, BscScan, Polygonscan).
- Make sure the source is verified. On EVM, explorers show “Contract Source Code Verified.” If not, treat with extreme caution.
3.2 Match the bytecode & metadata
- Look for the exact compiler version and optimization flags. Small mismatches can hide malicious changes.
- Prefer Sourcify “full match” verification when available.
3.3 Read constructor arguments / initializer params
For non-upgradeable contracts, check constructor parameters (owner, treasury, router, pair, fee rates). For proxies, check the initializer call.
4) Layer 2 — Architecture: proxies, modules, vaults, oracles
4.1 Proxies 101 (EIP-1967, Transparent, UUPS)
- Transparent proxy: Admin can upgrade; non-admin users interact with implementation. Check the ProxyAdmin owner.
- UUPS proxy: Upgrade logic lives in the implementation; a
upgradeTofunction is usually protected byonlyProxy/onlyOwner. - What you want: Multisig owner + public timelock + on-chain announce. If an EOA controls upgrades, your risk is centralized.
4.2 Modules commonly attached to tokens
- Router/AMM bindings: Uniswap/TraderJoe/Pancake routers. Check for addresses hard-coded vs configurable (configurable is both power and risk).
- Vaults (ERC-4626): Deposits/withdrawals mint/burn shares. Read conversion math; check for rounds of rounding that can leak value.
- Bridges & wrappers: Any cross-chain bridge dependency is a second-order risk you inherit.
- Oracles: Chainlink/Redstone/Pyth; confirm decimals, staleness checks, and fallback behavior.
4.3 Minimal architecture diagram
User → Token (ERC-20/721/4626) → [Optional Proxy] → Implementation
↘ Router/AMM Pair ↘ Vault/Strategy ↘ Oracle
Each arrow adds attack surface: approvals, external calls, math assumptions, upgrade paths.
5) Layer 3 — Roles & permissions: Ownable, AccessControl, multisigs & timelocks
5.1 Ownable / onlyOwner
Scan for:
transferOwnership: Is ownership a multisig? Which chain? Is there a timelock between proposal and execution?- Owner-gated setters:
setTax,setRouter,setPair,setBlacklist,setTrading,mint,burn.
5.2 AccessControl
- Roles:
DEFAULT_ADMIN_ROLE,MINTER_ROLE,PAUSER_ROLE,UPGRADER_ROLE. - Who holds them? If
DEFAULT_ADMIN_ROLEis an EOA, you’re trusting a person instead of a process.
5.3 Timelocks & multisigs
- Timelock (e.g., OZ TimelockController): Does governance delay risky actions?
- Multisigs: Safe (Gnosis) is standard. Confirm threshold and signer labeling. Look up the Safe on explorer.
6) Layer 4 — Token logic & economics: mints, burns, taxes, allowances
6.1 ERC-20 quick anatomy
function transfer(address to, uint256 amount) public returns (bool) {
_transfer(msg.sender, to, amount); // ← custom logic often hides here
return true;
}
function _transfer(address from, address to, uint256 amount) internal {
// look for: fees, blacklists, maxTx, maxWallet, tradingEnabled, pair checks
}
function _mint(address to, uint256 amount) internal { ... }
function _burn(address from, uint256 amount) internal { ... }
6.2 Dangerous knobs
- Mint after launch: Any owner/role can call
mintpost-launch? Tokenomics aren’t real. - Transfer tax: Taxes >10% are red flags; dynamic taxes that can be set to 100% enable honeypots.
- Blacklist/whitelist: Toggling sellability per address is textbook honeypot behavior.
- MaxTx/MaxWallet: Can lock you out of exiting during volatility if owner adjusts thresholds.
6.3 Allowances & Permit
approve/transferFromflows: Look for router allowances and Permit signatures (EIP-2612) that can create approvals off-chain.- Permit2 (Uniswap): shared allowance contracts. Great UX but concentrate risk; revoke when done.
6.4 ERC-4626 vault math (if staking/yield)
- Check
convertToShares/convertToAssets, rounding direction, and whether fees skim before or after conversion. - Read
totalAssetsdoes it include pending yields or only actual balances? MisstatedtotalAssetsdistorts pricing.
7) Layer 5 — External calls & failure modes: reentrancy, price oracles, slippage
7.1 Reentrancy & CEI
Look for the Checks-Effects-Interactions pattern. If the contract updates balances after making an external call, reentrancy risk exists.
// BAD (Effects after external)
callExternal(); // interaction
balances[msg.sender] -= x; // effects after - risky
// BETTER (CEI)
balances[msg.sender] -= x; // effects first
(bool ok,) = target.call(data); // interaction
require(ok);
7.2 Oracle & price manipulation
- Single-source oracle? Look for stale read checks (
updatedAt), min answer thresholds, or TWAP windows. - DEX price reads can be flash-manipulated unless using TWAP or depth-aware logic.
7.3 Slippage & sandwich resistance
- Routers should respect user slippage. Hard-coded minOut is a red flag.
- Private order flow / MEV protection at the app layer can help but isn’t a contract fix.
8) Red-flag patterns (with copy-paste indicators)
Indicator:
upgradeTo/upgradeToAndCall callable by EOA admin, no timelock.Defense: Require Safe multisig + on-chain timelock + public announcements.
Indicator:
onlyOwner mint remains enabled; _mint reachable via proxy.Defense: Mint disabled or irrevocably removed; supply caps enforced on-chain.
Indicator:
_transfer with blacklist/whitelist gates or dynamic 100% sell tax.Defense: Simulate sells and read
_transfer conditions end-to-end.
Indicator: Unrestricted
delegatecall to arbitrary targets or user-supplied data.Defense: Only controlled targets; strict allowlists; avoid user-controlled delegatecall.
Indicator: No staleness checks; trusting a single DEX spot price.
Defense: Chainlink/Pyth with staleness & bounds checks; TWAP windows.
9) Guided walkthroughs: ERC-20, upgradeable proxy, vault, AMM router
A) Token (ERC-20) with taxes
- Open explorer → Code tab → search
_transfer. Identify any of:tradingEnabled,isPair,_sellTax,_buyTax, blacklists. - Find setters like
setTax/excludeFromFee. Are theyonlyOwner? Can they set 100%? - Check mint/burn reachability. If owner can mint, tokenomics are mutable.
- Look at constructor: Where do router/pair addresses come from? Can owner swap router?
B) Upgradeable proxy (EIP-1967)
- Click “More Info → Is this a proxy?” → follow to ProxyAdmin. Verify admin ownership (Safe + threshold).
- Open implementation code → search
UUPSUpgradeableorTransparentUpgradeableProxypatterns. - Check
proxiableUUID(UUPS) andupgradeToprotection.
C) ERC-4626 vault
- Find
totalAssets,convertToShares/convertToAssets, fee hooks, deposit/withdraw flow. - Confirm rounding (up/down) and whether withdrawals can be blocked by a
pauseormaxLossgate without governance.
D) AMM router interaction
- Spot
swapExactTokensForTokens/swapExactETHForTokensusage. Min-out must be parameterized, not hard-coded. - Check for fee-on-transfer awareness if token has taxes; otherwise swaps can silently shortchange users.
10) Tools & how to use them in minutes
Etherscan, Basescan, BscScan, Polygonscan: source verify, holders, proxy admin, events, constructor args.
sourcify.dev “full match” = high confidence source ↔ bytecode mapping.
forge to run unit tests locally; import verified code and write 2–3 sanity tests in minutes.
Snippet: minimal Foundry test to sanity-check transfer & fee
// forge init token-check && cd token-check
// Put verified token source into src/Token.sol, then write:
pragma solidity ^0.8.24;
import "forge-std/Test.sol";
import "../src/Token.sol";
contract TokenSanity is Test {
Token t;
address alice = address(0xA1);
address bob = address(0xB2);
function setUp() public {
t = new Token(/* constructor args */);
t.transfer(alice, 1_000e18);
}
function testTransferNoHoneypot() public {
vm.prank(alice);
bool ok = t.transfer(bob, 100e18);
assertTrue(ok);
assertEq(t.balanceOf(bob) > 0, true, "tax may be 100%");
}
}
11) Printable checklist & risk scorecard
11.1 One-page checklist
- Verify source & bytecode match (explorer + Sourcify). Constructor/initializer params understood.
- Proxy?
- If yes: ProxyAdmin owner is a Safe multisig; changes go through a timelock; upgrade functions are guarded.
- Roles: Owner/admin roles → multisig;
MINTER_ROLEoff or tightly bounded;PAUSERSlimited. - Token logic: No post-launch mint; taxes ≤10% and fixed; no blacklists/whitelists;
_transferhas no sell-only reverts. - Vault/Router: MinOut param present; rounding sensible; no unsafe external calls before state updates.
- Oracles: Staleness & bounds checks; not a single DEX spot; has TWAP or trusted feed.
- Docs: Audits hosted on the auditor’s domains; upgrade policy documented; governance transparent.
11.2 Risk scorecard (0–5 each)
| Dimension | Questions | Score (0–5) |
|---|---|---|
| Verification | Source/bytecode perfect match? Compiler & settings consistent? | |
| Upgrade Safety | Proxy admin multisig + timelock? Public ceremonies? | |
| Permissions | No EOA superpowers? Roles least-privilege? | |
| Token Safety | No mint/burn abuse; taxes bounded; no blacklist/whitelist traps. | |
| External Risk | Reentrancy guarded; oracle robust; slippage parameters respected. |
Interpretation: ≥20/25 = proceed with caution; 16–19/25 = small pilot only; ≤15/25 = skip.
12) FAQ & mental models
Do audits make code safe?
What if ownership is “renounced”?
Ownable means little if a proxy admin still controls upgrades. Follow the proxy trail.How deep must I go if I’m not technical?
Is fee-on-transfer always bad?
13) External resources & official docs
- Solidity Docs — language reference and security considerations.
- OpenZeppelin Contracts — standard libraries; read the docs for Ownable, AccessControl, Pausable, UUPS.
- EIP-1967 (Proxy Storage Slots), EIP-1822 (UUPS), EIP-2612 (Permit), EIP-4626 (Tokenized Vaults).
- Etherscan, Basescan, BscScan, Polygonscan — verification + proxy admin trails.
- Sourcify — source ↔ bytecode verification (full match).
- Slither | Mythril | Semgrep — static analysis.
- Tenderly | Phalcon — simulation & trace tools.
- Foundry — build quick tests to reproduce logic locally.
- Ethereum.org Security — secure patterns and common pitfalls.
Recap
- Reading code is a process, not a vibe. Start at verification, then architecture, permissions, token logic, and external risks.
- Kill the deal early if you see an EOA upgrade admin, post-launch minting, blacklist/whitelist gates, or honeypot-style taxes.
- Simulate real actions (buy→sell, deposit→withdraw), not just reads. Document what you find and stick to your risk scorecard.
Quick check
- How do you verify that “renounced” ownership isn’t hiding a proxy admin?
- Which three functions do you search first in an ERC-20 to spot honeypots?
- What’s the simplest way to confirm slippage is respected in router calls?
- Why does rounding direction matter in ERC-4626 vaults?
- What does a Safe multisig + timelock guarantee that an EOA admin cannot?
Show answers
- Click “Is this a proxy?” → open ProxyAdmin → verify owner is a multisig + timelock, not an EOA.
_transfer, tax setters (e.g.,setTax), blacklist/whitelist toggles.- Ensure router methods accept user-supplied
amountOutMinand test via simulator. - It determines who benefits from dust: users or the protocol; consistent rounding prevents value leakage.
- It forces delay and multiple signers for upgrades/changes, lowering key compromise and rogue-admin risk.