Skip to main content
Solidity, as the primary language for Ethereum smart contracts, has several common anti-patterns that can lead to security vulnerabilities, excessive gas costs, and maintenance issues. Here are the most important anti-patterns to avoid when writing Solidity code.
Always follow the Checks-Effects-Interactions pattern to prevent reentrancy attacks. Update all internal state before making external calls, and consider using reentrancy guards for complex functions.
Always check the return values of low-level calls like send(), call(), and delegatecall(). These functions return a boolean indicating success or failure, and ignoring this value can lead to silent failures.
In Solidity versions before 0.8.0, always use SafeMath or similar libraries to prevent integer overflow and underflow. In Solidity 0.8.0 and later, arithmetic operations revert on overflow/underflow by default, but use unchecked blocks carefully for gas optimization.
Implement proper access control for sensitive functions. Use modifiers to restrict access to authorized users, and consider using role-based access control for more complex applications.
Be cautious when using block.timestamp for critical logic, as miners can manipulate it slightly. For applications requiring precise timing, consider using block numbers instead or be aware of the potential for small timestamp manipulations.
Avoid using tx.origin for authorization, as it makes your contract vulnerable to phishing attacks. Use msg.sender instead to verify the immediate caller of your contract.
Avoid unbounded loops that iterate over arrays of unknown size. Use bounded loops with pagination to prevent out-of-gas errors, especially when the array size can grow indefinitely.
Pay attention to the order of inheritance in Solidity. The order is from most derived (right) to most base (left), and super calls are resolved from right to left. Incorrect inheritance order can lead to unexpected behavior.
Optimize your code for gas efficiency. Avoid unnecessary storage operations, minimize copying data to memory, and use gas-efficient patterns like pre-allocating array space. In Solidity 0.8.0+, use unchecked blocks for simple arithmetic operations when overflow/underflow is not a concern.
Avoid hardcoding addresses in your contracts. Use constructor parameters to initialize addresses and provide functions to update them if needed. This makes your contracts more flexible and easier to deploy to different environments.
Emit events for important state changes in your contract. Events provide an audit trail and allow off-chain applications to track what’s happening on the blockchain. They are essential for building user interfaces and monitoring contract activity.
Handle decimals properly in your contracts. Solidity doesn’t support floating-point numbers, so use integer math with appropriate scaling factors. Consider expressing rates as “X per Y” to avoid division, which can lead to rounding errors.
Protect the selfdestruct function with proper access control. An unprotected selfdestruct can allow anyone to destroy your contract and steal its funds.
Don’t rely on block variables like block.timestamp or block.difficulty for randomness, as they can be manipulated by miners. For applications requiring randomness, use a commit-reveal scheme, oracles, or VRF (Verifiable Random Function) services like Chainlink VRF.
Validate all inputs to your functions. Check for zero addresses, valid ranges for numeric values, and other constraints specific to your application. Proper input validation prevents many common errors and vulnerabilities.
Use the SafeERC20 library when interacting with ERC20 tokens. Some tokens don’t follow the standard correctly and might not return a boolean on transfer() or approve() calls, which can cause your transactions to revert unexpectedly.