Solidity Deep Dive: Writing Secure Smart Contracts
Go beyond Hello World - learn advanced Solidity patterns, common vulnerabilities, and how to write production-grade, auditable smart contracts.
Solidity Deep Dive: Writing Secure Smart Contracts
Writing Solidity is easy. Writing secure Solidity is hard. This guide covers the patterns and pitfalls that separate production-grade contracts from vulnerable ones.
Understanding data locations is critical for both security and gas efficiency:
solidity// storage: persists on-chain (expensive) string public storedName; // memory: temporary, cleared after function call function process(string memory name) public pure returns (string memory) { return name; } // calldata: read-only, most gas-efficient for external inputs function processCalldata(string calldata name) external pure returns (uint256) { return bytes(name).length; }
The infamous DAO hack that lost $60M. Occurs when an external call is made before state is updated.
Vulnerable code:
solidityfunction withdraw() public { uint amount = balances[msg.sender]; (bool success, ) = msg.sender.call{value: amount}(""); // external call FIRST require(success); balances[msg.sender] = 0; // state updated AFTER - too late! }
Fixed with Checks-Effects-Interactions pattern:
solidityfunction withdraw() public { uint amount = balances[msg.sender]; balances[msg.sender] = 0; // 1. Update state first (bool success, ) = msg.sender.call{value: amount}(""); // 2. Then interact require(success); }
Alternatively, use OpenZeppelin's
ReentrancyGuardsolidityimport "@openzeppelin/contracts/security/ReentrancyGuard.sol"; contract MyContract is ReentrancyGuard { function withdraw() public nonReentrant { ... } }
Pre-Solidity 0.8.0, arithmetic could silently overflow. Now it reverts by default. Still relevant in
uncheckedsolidity// Safe in 0.8+ uint256 x = type(uint256).max + 1; // reverts // Dangerous - only use when you've proven safety unchecked { uint256 y = type(uint256).max + 1; // overflows to 0 }
solidity// WRONG - anyone can call this function mint(address to, uint256 amount) public { _mint(to, amount); } // CORRECT - restrict to owner function mint(address to, uint256 amount) public onlyOwner { _mint(to, amount); }
Use OpenZeppelin
OwnableAccessControlDon't use spot prices from DEXes as oracles - they can be manipulated in a single transaction (flash loans).
Use instead: Chainlink price feeds with heartbeat checks, or Uniswap v3 TWAPs (time-weighted average prices).
Transactions sit in the mempool before confirmation. Bots can see your transaction and insert theirs first.
Mitigation: Commit-reveal schemes, minimum slippage parameters, or private mempools (Flashbots Protect).
solidity// Use uint256 instead of smaller types when possible (no packing needed) uint256 value; // more gas efficient than uint8 in isolation // Pack structs to fit in 32-byte slots struct Packed { uint128 a; // 16 bytes uint128 b; // 16 bytes - both fit in ONE slot } // Use custom errors (cheaper than string messages) error InsufficientBalance(uint256 available, uint256 required); function withdraw(uint256 amount) public { if (balances[msg.sender] < amount) { revert InsufficientBalance(balances[msg.sender], amount); } } // Cache storage reads uint256 cachedBalance = balances[msg.sender]; // 1 SLOAD if (cachedBalance > 100) { ... } // no additional SLOAD
Use the proxy pattern (OpenZeppelin Upgrades) to deploy upgradeable contracts without losing state:
- Transparent Proxy: Admin and users use different selectors
- UUPS: Upgrade logic in the implementation contract
- Beacon Proxy: Multiple proxies share one implementation
Upgradeability introduces centralisation - ensure multisig control.
Use Foundry (fast, written in Solidity) or Hardhat (JS ecosystem):
bash# Foundry forge test -vvv # verbose output forge coverage # line coverage forge fuzz --runs 1000 # fuzz testing # Hardhat npx hardhat test npx hardhat coverage
- [ ] All functions have access control
- [ ] Reentrancy guards on external calls
- [ ] No hardcoded addresses
- [ ] Events emitted for all state changes
- [ ] Slippage/deadline params on DEX interactions
- [ ] Emergency pause mechanism (for high-value contracts)
- [ ] Professional audit completed
- [ ] Bug bounty launched
Security in smart contracts is non-negotiable - there are no patches once funds are lost.
Found this guide helpful? Share it with others


