Security Best Practices
Essential security guidelines for developing secure smart contracts
Why Security Matters
Smart contracts handle valuable assets and execute critical business logic. A single vulnerability can lead to catastrophic losses. Following security best practices is not optional—it's essential for protecting users and maintaining trust in your protocol.
These guidelines are based on common vulnerabilities found in production contracts and recommendations from security experts. Always audit your contracts before deployment, especially for production use.
Access Control
Use Access Control Modifiers
Always use OpenZeppelin's Ownable or AccessControl for managing permissions. Never hardcode addresses.
Example:
Use `onlyOwner` modifier or role-based access control instead of direct address checks.Implement Multi-Signature Wallets
For critical operations, require multiple signatures before execution.
Example:
Use Gnosis Safe or implement custom multi-sig for admin functions.Avoid tx.origin
Never use `tx.origin` for authorization. Always use `msg.sender` instead.
Example:
`require(msg.sender == owner)` instead of `require(tx.origin == owner)`.Reentrancy Protection
Use Checks-Effects-Interactions Pattern
Always follow the CEI pattern: check conditions, update state, then interact with external contracts.
Example:
Update balances before calling external contracts to prevent reentrancy attacks.Implement ReentrancyGuard
Use OpenZeppelin's ReentrancyGuard modifier for functions that call external contracts.
Example:
Add `nonReentrant` modifier to functions that perform external calls.Avoid External Calls in Loops
External calls in loops can lead to gas issues and reentrancy vulnerabilities.
Example:
Batch external calls or use pull payment patterns instead.Integer Overflow/Underflow
Use SafeMath or Solidity 0.8+
Always use SafeMath library or Solidity 0.8+ which has built-in overflow protection.
Example:
Use `SafeMath.add()` or rely on Solidity 0.8+ automatic checks.Validate Input Ranges
Always validate that inputs are within expected ranges before performing arithmetic.
Example:
Check that amounts are greater than 0 and less than maximum before calculations.Gas Optimization
Use Packed Storage
Pack multiple small variables into single storage slots to save gas.
Example:
Use `uint128` instead of `uint256` when values are small enough.Cache Storage Variables
Cache frequently accessed storage variables in memory to reduce gas costs.
Example:
Store `balances[user]` in a local variable if accessed multiple times.Use Events Instead of Storage
For data that doesn't need to be read on-chain, use events instead of storage.
Example:
Emit events for historical data that can be indexed off-chain.Input Validation
Validate All External Inputs
Always validate inputs from users, external contracts, and oracles.
Example:
Check that addresses are not zero, amounts are positive, and arrays are not empty.Use Require Statements with Clear Messages
Provide clear error messages in require statements for better debugging.
Example:
`require(amount > 0, 'Amount must be greater than zero')`.Sanitize String Inputs
Validate and sanitize string inputs to prevent injection attacks.
Example:
Check string length and validate format before processing.Additional Security Resources
Security Tools
- • Slither - Static analysis tool
- • Mythril - Security analysis framework
- • Echidna - Property-based fuzzer
- • Manticore - Symbolic execution tool
Security Standards
- • OpenZeppelin Contracts - Secure library
- • Consensys Best Practices
- • SWC Registry - Vulnerability database
- • DeFi Security Standards