Smart Contract Risks: Re-entrancy, Oracle Manipulation, Access Control, Math Bugs, MEV, and Defense Checklist
Smart contract risks are usually not random. The same vulnerability classes appear again and again across DeFi protocols, NFT contracts, staking systems, vaults, bridges, token launches, and governance modules. Re-entrancy breaks accounting. Oracle manipulation breaks pricing. Weak access control breaks trust. Math bugs leak value. MEV changes execution assumptions. This TokenToolHub guide explains the top smart contract vulnerability classes, the standard production defenses, and the pre-mainnet checklist every builder should run before real users and real liquidity arrive.
TL;DR
- Most smart contract exploits are preventable when teams recognize common vulnerability patterns before deployment.
- Use Checks-Effects-Interactions, ReentrancyGuard, pull payments, and malicious callback tests to reduce re-entrancy risk.
- Do not use single-block spot AMM prices for safety-critical logic such as minting, borrowing, liquidations, or redemptions.
- Use strong oracle design: TWAP, medianization, secured feeds, staleness checks, deviation limits, and circuit breakers.
- Access control should be explicit, narrow, logged, and delay-gated where possible. Never use
tx.originfor authorization. - Solidity 0.8+ reduces overflow and underflow risk, but rounding, decimals, scaling, unchecked blocks, and divide-by-zero still matter.
- MEV and front-running affect swaps, auctions, liquidations, mints, permits, and public mempool transactions.
- Security is not only code. It includes testing, fuzzing, invariant design, runbooks, audits, monitoring, and disclosure.
A smart contract exploit often looks sudden, but the vulnerability usually existed long before the attacker arrived. The dangerous assumption might be that a callback cannot re-enter, a price cannot be moved, an owner key will never be compromised, an upgrade will never fail, or a pool will always have enough liquidity. Security review is the process of finding those assumptions before mainnet value tests them.
This guide is educational. It is not a formal audit, legal advice, investment advice, or a guarantee of contract safety. Production systems should use internal review, static analysis, fuzzing, invariant testing, external audits, bug bounties, monitoring, and incident response planning.
Smart contract risk overview
Smart contracts are unforgiving because they turn code into financial infrastructure. Once deployed, a small mistake can directly control deposits, withdrawals, liquidations, swaps, vault shares, NFT approvals, governance rights, or upgrade permissions. Unlike traditional software, the attacker does not need your server password. If the public contract exposes a profitable state transition, the chain will execute it.
That is why production smart contract security is less about memorizing isolated bugs and more about recognizing failure classes. Re-entrancy, oracle manipulation, access control, math issues, MEV, and denial-of-service risk are not one-off topics. They are recurring patterns that appear in different forms across protocols.
1. Re-entrancy
Re-entrancy happens when your contract calls an external address before finishing its own state updates. The external address may be an EOA, another contract, a token receiver, a malicious fallback, or a callback-enabled token standard. If that external address calls back into your contract while balances, shares, or status flags are still stale, it can exploit the temporary inconsistency.
The classic example is a withdrawal function that sends ETH first and sets the user's balance to zero afterward. The receiving contract can re-enter withdraw() before the balance is cleared, causing repeated withdrawals. But modern re-entrancy is not limited to basic ETH transfers. ERC-777 hooks, ERC-721 receiver callbacks, ERC-1155 receiver callbacks, cross-function shared state, and read-only pricing paths can all create less obvious re-entrancy exposure.
Re-entrancy variants that matter
- Classic re-entrancy: an attacker re-enters the same function before state is finalized.
- Cross-function re-entrancy: an attacker re-enters through another function that mutates or reads the same shared state.
- Read-only re-entrancy: a view function returns distorted state during a callback and another system trusts the value.
- Token hook re-entrancy: ERC-777, ERC-721, ERC-1155, and receiver callbacks execute code during transfers.
- Fallback re-entrancy: sending ETH to a contract triggers
receive()orfallback(), allowing arbitrary logic.
// Vulnerable pattern: external call first
function withdraw() external {
uint256 bal = balances[msg.sender];
require(bal > 0, "Zero");
(bool ok,) = msg.sender.call{value: bal}("");
require(ok, "Fail");
balances[msg.sender] = 0;
}
// Safer pattern: effects first, interaction last
function withdraw() external nonReentrant {
uint256 bal = balances[msg.sender];
require(bal > 0, "Zero");
balances[msg.sender] = 0;
(bool ok,) = msg.sender.call{value: bal}("");
require(ok, "Fail");
}
Checks-Effects-Interactions
Checks-Effects-Interactions, usually shortened to CEI, is the first defensive pattern. The contract checks conditions, updates internal state, then calls external systems last. It does not remove every possible bug, but it prevents many obvious re-entrancy failures.
- Checks: verify permissions, balances, deadlines, input ranges, and state.
- Effects: update balances, shares, flags, counters, and accounting.
- Interactions: call external addresses, transfer tokens, send ETH, or invoke other protocols.
Testing re-entrancy properly
Do not only inspect code manually. Write a malicious test contract that calls back into your system. Test sequences of deposit, withdraw, transfer, claim, repay, borrow, and liquidate. Add invariants such as total assets must always equal or exceed total liabilities.
Cross-function re-entrancy can bypass a narrow defense. If multiple external functions mutate the same accounting state, review them as one shared attack surface.
2. Oracle manipulation
Oracle manipulation happens when a smart contract relies on a price or external value that an attacker can move or delay. If a protocol uses a manipulable price for collateral value, mint amounts, redemptions, liquidations, swaps, or vault accounting, attackers can convert price distortion into profit.
The most dangerous pattern is using a single-pool AMM spot price inside a safety-critical function. A flash loan can push the pool price for one transaction, trigger favorable protocol logic, then unwind the manipulation before the transaction ends.
Common oracle risk sources
- Thin AMM liquidity: low liquidity pools can be moved cheaply.
- Single-pool spot prices: same-transaction reserve reads are often unsafe for core logic.
- Stale prices: a feed may be old, frozen, or delayed.
- No deviation checks: abnormal price jumps pass without circuit breakers.
- Decimal mismatches: feeds and tokens use different scales.
- Cross-domain delays: L2 or bridge-delivered prices may depend on sequencer liveness or message timing.
// Pseudocode: basic sanity checks around an oracle read
Price memory p = oracle.latest();
require(block.timestamp - p.timestamp <= MAX_STALE, "stale");
uint256 delta = abs(p.value - last.value) * 1e18 / last.value;
require(delta <= MAX_CHANGE, "jump");
Oracle defenses
- Use secured, aggregated feeds for major assets where possible.
- Use TWAP or medianization if relying on DEX data.
- Check staleness, heartbeat, decimals, and update timestamps.
- Set maximum deviation bounds and circuit breakers.
- Use secondary price checks for critical operations.
- Cap LTVs and slow reaction functions when feeds are immature.
- Document trust assumptions when prices come from bridges, sequencers, or other domains.
A price can be on-chain and still be manipulable. Safety depends on liquidity depth, aggregation, time weighting, freshness, deviation checks, and how much value the contract lets that price control.
3. Access control
Access control decides who can do what. Many catastrophic incidents come from missing modifiers, overpowered owners, exposed upgrade keys, weak multisig practices, bad initializer flows, or forgotten admin functions. The code may work exactly as written, but the permissions are wrong.
Common access control failures
- Missing role checks: anyone can call a privileged function.
- Overpowered owner: one key can mint, pause, upgrade, drain, and change fees.
- Misconfigured initializer: ownership is unset, claimable, or initialized twice.
- Unsafe upgrade authority: logic can be replaced without governance control.
- tx.origin authorization: phishing through intermediate contracts can bypass user expectations.
- No event logging: privileged actions happen without clear monitoring signals.
// Basic role-gated pattern
modifier onlyOwner() {
require(msg.sender == owner, "!owner");
_;
}
modifier onlyRole(bytes32 role) {
require(hasRole(role, msg.sender), "!role");
_;
}
// Better in production:
// separate roles for pauser, upgrader, treasurer, minter, rate setter
Access control defenses
- Use
msg.senderand explicit role checks. Do not usetx.originfor auth. - Use Ownable2Step or role-based access control for safer administration.
- Separate pauser, upgrader, treasurer, rate setter, oracle manager, and minter roles.
- Route sensitive actions through a multisig and timelock.
- Emit events for privileged actions.
- Keep emergency pause narrow and auditable.
- Publish a roles matrix so users know what admin powers exist.
Some protocols need emergency controls during early stages. The issue is whether those controls are transparent, scoped, delayed where possible, and protected by strong key management.
4. Math, overflow, underflow, rounding, and scale bugs
Solidity 0.8+ reverts on overflow and underflow by default. That removed many classic arithmetic bugs, but it did not remove math risk. Modern math bugs usually come from rounding direction, decimal mismatches, unchecked blocks, divide-by-zero, fixed-point scaling, share conversion, reward accrual, or precision loss.
Math risk patterns
- Integer division floors: Solidity division truncates toward zero.
- Wrong rounding direction: a protocol may unintentionally favor attackers across repeated operations.
- Decimal mismatch: USDC-style 6 decimals and ETH-style 18 decimals require normalization.
- Divide-by-zero: empty vaults, zero shares, or zero liquidity can break formulas.
- Unchecked blocks: unsafe assumptions can bypass overflow protection.
- Ad-hoc scaling: duplicate hand-written fixed-point math across files increases bug risk.
// Example: rounding up division
function divUp(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0, "DIV0");
return (a + b - 1) / b;
}
Math defenses
- Document units and scales in comments.
- Normalize token decimals before computing exchange rates.
- Use well-reviewed
mulDivand fixed-point helper libraries. - Choose rounding direction deliberately and test it.
- Validate denominators before division.
- Use SafeCast where narrowing casts occur.
- Keep
uncheckedblocks minimal and prove bounds with tests. - Fuzz edge cases for zero amounts, dust, max values, tiny shares, and unusual decimals.
5. MEV and front-running
MEV, or maximal extractable value, comes from the ability of validators, builders, searchers, and bots to reorder, insert, delay, or react to transactions. Any transaction visible in the public mempool can be copied, sandwiched, back-run, or front-run if it creates profit.
Protocols cannot make public mempools disappear, but they can reduce harmful ordering dependence. User interfaces can also protect users by enforcing slippage bounds, deadlines, private order flow options, and clearer simulations.
Where MEV appears
- Swaps: sandwich attacks extract value from user slippage.
- Liquidations: bots compete for liquidation profit and gas priority.
- NFT mints: copycats or bots front-run desirable mints.
- Oracle updates: searchers back-run price changes.
- Auctions: visible bids can be copied or outbid strategically.
- Permits and approvals: signatures can create unexpected ordering and replay concerns if poorly designed.
// Pattern: user-bounded swap
function swap(
address tokenIn,
address tokenOut,
uint256 amountIn,
uint256 minOut,
uint256 deadline
) external {
require(block.timestamp <= deadline, "expired");
// compute amountOut...
require(amountOut >= minOut, "slippage");
// transfer and settle...
}
MEV defenses
- Require
minOut,maxIn, and deadline parameters where relevant. - Reject stale transactions.
- Use commit-reveal for sealed bids, sensitive mints, or whitelist actions.
- Use batch auctions or uniform clearing prices for certain markets.
- Offer private transaction submission options in the UI.
- Show users simulations before signing.
- Use conservative default slippage.
- Validate EIP-2612 permit domain separators, nonces, expiries, and chain IDs.
Users often treat slippage as a convenience setting, but high slippage can become a direct invitation for sandwich attacks. Protocols and UIs should make safe defaults the normal path.
6. Token safety pitfalls
Token integrations are a major source of subtle bugs. Not every ERC-20 behaves exactly like a standard textbook token. Some tokens charge transfer fees, rebase, pause, blacklist, return no boolean value, or call hooks in ways that surprise protocols.
Non-standard token patterns
- Fee-on-transfer tokens: amount sent may not equal amount received.
- Rebasing tokens: balances can change without direct transfers.
- Blacklist tokens: transfers can fail based on address status.
- Pausable tokens: transfers can stop unexpectedly.
- No-return tokens: some older tokens do not return boolean values cleanly.
- Callback tokens: token transfers can invoke receiver logic.
Token integration defenses
- Use SafeERC20 where appropriate.
- Measure before and after balances if exact received amount matters.
- Avoid approving
type(uint256).maxby default in user-facing flows. - Document unsupported token types.
- Test with mock fee-on-transfer, rebasing, and no-return tokens.
- Review token permissions before integrating unknown assets.
Check token controls before integrating or buying
Before trusting a token, scan for mint functions, blacklist logic, pause controls, fee changes, ownership, proxy upgrades, and sell restrictions.
7. Upgrade safety
Upgradeable contracts give teams flexibility, but they also create governance and storage risks. A protocol with upgrade power is not purely immutable. Users are trusting the upgrade process, the admin keys, the implementation logic, and the storage layout discipline of the team.
Upgrade risks
- Implementation contract left initializable.
- Initializer called twice.
- Storage layout corrupted by variable reordering.
- Upgrade function not properly restricted.
- No timelock or public review period for high-impact changes.
- No rollback plan if the upgrade breaks production state.
Upgrade defenses
- Use versioned initializers.
- Lock implementation contracts where required.
- Preserve storage gaps.
- Snapshot and compare storage layout before upgrades.
- Put upgrades behind multisig and timelock.
- Emit upgrade events.
- Rehearse upgrade runbooks before production changes.
8. Testing depth
A smart contract test suite should do more than confirm that happy paths work. Attackers do not follow happy paths. A strong testing workflow includes unit tests, integration tests, fork tests, fuzzing, invariant tests, negative tests, gas reports, coverage tracking, and CI automation.
Testing layers
- Unit tests: confirm isolated functions behave as expected.
- Integration tests: test multiple contracts and dependencies together.
- Fork tests: simulate against real mainnet state, real tokens, real pools, and real oracles.
- Fuzz tests: explore many input combinations automatically.
- Invariant tests: prove that core accounting properties remain true across random action sequences.
- Negative tests: confirm forbidden behavior fails correctly.
Security testing workflow:
1. Unit tests for normal behavior
2. Negative tests for forbidden behavior
3. Integration tests across contracts
4. Mainnet-fork tests for real dependencies
5. Fuzz tests for input edge cases
6. Invariant tests for accounting truth
7. Static analysis with Slither
8. Coverage and gas reports in CI
9. External review or audit before launch
10. Bug bounty and monitoring after launch
9. Defense checklist
Use this section as a pre-mainnet gate. If any item is red, slow down. A delayed launch is cheaper than a preventable exploit.
Pre-mainnet security checklist
- Checks-Effects-Interactions applied across functions with external calls.
- ReentrancyGuard used on external mutators that touch shared state.
- Pull-payment pattern used where distributions or refunds are involved.
- Malicious callback tests written for re-entrancy paths.
- Oracle design avoids unsafe single-block spot price reads.
- TWAP, median, secured feed, staleness checks, and deviation bounds applied where needed.
- Oracle trust assumptions documented, especially for L2 or cross-domain prices.
- Access control uses least-privilege roles.
- Ownership transfer uses a safe two-step process where relevant.
- Admin keys are protected by multisig and timelock.
- Privileged actions emit events.
- Emergency pause is narrow, auditable, and documented.
- Math units are documented.
- Rounding direction is chosen and tested.
- Decimals are normalized.
- Unchecked blocks are minimal and justified.
- SafeERC20 used for token interactions.
- Fee-on-transfer, rebasing, and non-standard token behavior considered.
- Upgradeable storage layout snapshotted and tested.
- Initializers and upgrade authorization reviewed.
- Fuzz, invariant, fork, and negative tests are included.
- Runbooks exist for pause, unpause, upgrade, rollback, and incident communication.
- Bug bounty, audit report, and public risk docs are ready before meaningful TVL.
10. Quick check
Use these questions to confirm the core ideas before moving into audits and testing workflows.
| Question | Short answer |
|---|---|
| What does CEI stand for? | Checks-Effects-Interactions. Check conditions, update internal state, then make external calls last. |
Why is tx.origin unsafe for authorization? |
It can be abused through intermediate contracts and phishing-style flows. Use msg.sender plus explicit roles instead. |
| Name two ways to harden price oracles. | Use secured aggregated feeds, AMM TWAP or medianization, staleness checks, deviation checks, and circuit breakers. |
| Why does Solidity 0.8 not remove math risk? | Overflow checks help, but rounding, decimals, scale mismatch, divide-by-zero, share accounting, and unchecked blocks can still fail. |
Why do user swaps need minOut and deadlines? |
They bound slippage and prevent stale transactions from executing under worse conditions than the user intended. |
11. Go deeper
Smart contract security is a practice. Reading vulnerability writeups helps, but hands-on testing, breaking intentionally vulnerable contracts, and building your own checklists make the lessons stick.
- OpenZeppelin Contracts for secure smart contract libraries.
- OpenZeppelin Upgrades Docs for proxy and upgrade patterns.
- OpenZeppelin Audit Blog for audit learnings and security research.
- Ethereum.org Smart Contract Security for general security best practices.
- Slither for static analysis.
- Foundry Book for testing, fuzzing, fork tests, and invariant workflows.
- Hardhat for smart contract development and testing.
- Damn Vulnerable DeFi for hands-on DeFi security puzzles.
- Cyfrin Updraft for Solidity and smart contract security training.
Verdict: know the patterns before mainnet value arrives
Smart contract risk is easier to reduce when teams treat security as part of design, not only a final review. Re-entrancy is a state-ordering problem. Oracle manipulation is a trust and liquidity problem. Access control is a governance and key-management problem. Math bugs are accounting problems. MEV is an ordering problem. Upgrades are an authority problem.
The safest teams do not depend on one defense. They stack multiple layers: secure design, known libraries, role separation, guarded external calls, robust oracles, invariant tests, fork simulations, static analysis, audit review, bug bounties, monitoring, and response runbooks.
If your protocol will hold user funds, every assumption deserves pressure. Who can call this? What can re-enter? Who controls the price? What if this token charges a fee? What if the oracle is stale? What if the admin key is compromised? What if the transaction is front-run? What if a loop grows too large? What if the upgrade corrupts storage?
Answer those questions before deployment. Mainnet will answer them for you if you do not.
Build with a security-first review process
Before deploying or interacting with a contract, review re-entrancy, oracle assumptions, access control, math, MEV exposure, token behavior, upgrades, and emergency procedures.
FAQs
What are the biggest smart contract risks?
The biggest recurring risks include re-entrancy, oracle manipulation, weak access control, math and rounding bugs, unsafe upgrades, MEV exposure, token integration issues, and denial-of-service paths.
What does CEI mean in smart contract security?
CEI means Checks-Effects-Interactions. It tells developers to validate conditions first, update internal state second, and make external calls last to reduce re-entrancy risk.
Why is oracle manipulation dangerous?
Oracle manipulation is dangerous because a manipulated price can affect borrowing power, liquidations, minting, redemptions, swaps, and vault accounting. A bad price can turn protocol logic into an attacker payout.
Why should contracts avoid tx.origin?
tx.origin can be abused through intermediate contracts and phishing flows. Authorization should use msg.sender with explicit owner or role checks.
Does Solidity 0.8 prevent all math bugs?
No. Solidity 0.8 helps prevent overflow and underflow by default, but rounding, decimals, scale mismatch, unchecked blocks, divide-by-zero, and share accounting bugs still matter.
How can protocols reduce MEV risk?
Protocols can use slippage bounds, deadlines, commit-reveal schemes, batch auctions, private transaction options, simulations, and auction designs that reduce harmful ordering dependence.
What testing should smart contracts use before mainnet?
Use unit tests, integration tests, fork tests, fuzz tests, invariant tests, negative tests, static analysis, gas and coverage checks, external audits, and incident response rehearsals.
What should a pre-mainnet defense checklist include?
It should include re-entrancy hardening, oracle robustness, access control, math sanity, token integration safety, upgrade checks, testing depth, documentation, audits, bug bounties, monitoring, and runbooks.
Final reminder: most smart contract incidents are not magic. They are patterns. Learn the patterns, test the assumptions, protect the keys, verify the data, and rehearse the failure response before mainnet value arrives. Check first, then decide.