๐ก๏ธ 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
| Issue | What it is | Mitigation |
|---|---|---|
| Reentrancy | External call before state update lets attacker re-enter and drain funds | Checks-Effects-Interactions; use ReentrancyGuardย |
| Integer overflow/underflow | Solidity < 0.8 had no built-in checks | Use Solidity 0.8+ or SafeMath-style libraries |
| Access control | Sensitive functions callable by anyone | Ownableย , AccessControlย |
| Front-running | Miner or bot sees tx in mempool and copies or front-runs it | Use commit-reveal, private mempools, or design so front-running is unprofitable |
| Oracle manipulation | Relying on a single price source that can be gamed | Use decentralized oracles (e.g. Chainlink); avoid spot-only or manipulable sources |
| Signature replay | Reusing a signature on another chain or after use | Include chainId, nonce, deadline; use EIP-712ย |
| Delegatecall / storage | Wrong storage layout or untrusted target in delegatecall | Use 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
- Consensys: Smart Contract Best Practicesย
- SWC Registry (Smart Contract Weakness Classification)ย
- OpenZeppelin Contractsย
- Slitherย โ Static analysis
- Ethers.js securityย โ Avoid signing unchecked data
Last updated on