Skip to Content
๐ŸŒ Web3๐Ÿ“– EVM๐Ÿ›ก๏ธ Smart Contract Security

๐Ÿ›ก๏ธ Smart Contract Security

Smart contracts on the EVM are immutable once deployed (unless you use proxy contracts). Bugs can lock funds or enable theft. Following security practices and using battle-tested patterns reduces risk.


๐ŸŽฏ Principles

  • Assume the contract will hold value โ€” Design for malicious callers and edge cases
  • Reuse audited code โ€” OpenZeppelinย , Solmateย 
  • Test thoroughly โ€” Unit tests, fuzzing, and mainnet forking
  • Get a professional audit for anything holding significant value

โš ๏ธ Common Vulnerabilities

IssueWhat it isMitigation
ReentrancyExternal call before state update lets attacker re-enter and drain fundsChecks-Effects-Interactions; use ReentrancyGuardย 
Integer overflow/underflowSolidity < 0.8 had no built-in checksUse Solidity 0.8+ or SafeMath-style libraries
Access controlSensitive functions callable by anyoneOwnableย , AccessControlย 
Front-runningMiner or bot sees tx in mempool and copies or front-runs itUse commit-reveal, private mempools, or design so front-running is unprofitable
Oracle manipulationRelying on a single price source that can be gamedUse decentralized oracles (e.g. Chainlink); avoid spot-only or manipulable sources
Signature replayReusing a signature on another chain or after useInclude chainId, nonce, deadline; use EIP-712ย 
Delegatecall / storageWrong storage layout or untrusted target in delegatecallUse proxies correctly; never delegatecall to user-controlled addresses

โœ… Safe Patterns

Checksโ€“Effectsโ€“Interactions

Update state before external calls:

// BAD: external call before state change function withdraw() external { (bool ok,) = msg.sender.call{value: balances[msg.sender]}(""); require(ok); balances[msg.sender] = 0; // too late โ€” reentrancy } // GOOD: state change first function withdraw() external { uint256 amount = balances[msg.sender]; balances[msg.sender] = 0; (bool ok,) = msg.sender.call{value: amount}(""); require(ok); }

Use OpenZeppelin

import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/security/Pausable.sol"; contract MyContract is Ownable, ReentrancyGuard, Pausable { function critical() external onlyOwner whenNotPaused nonReentrant { // ... } }

Validate Inputs and Use Safe Math

Solidity 0.8+ reverts on overflow/underflow. Still validate inputs (e.g. non-zero address, bounds):

require(to != address(0), "zero address"); require(amount > 0 && amount <= balanceOf[msg.sender], "bad amount");

๐Ÿ”— Resources

Last updated on