ConsenSys Diligence Smart Contract Best Practices
repository·master·Indexed 27 days ago
https://github.com/consensysdiligence/smart-contract-best-practicesA comprehensive collection of smart contract security best practices maintained by ConsenSys Diligence. This guide helps developers write secure blockchain code by documenting common attack vectors—including Denial of Service (DoS), force-feeding, front-running, and griefing—and providing mitigation strategies such as pull payment systems, commit-reveal schemes, and internal balance tracking.
What's inside smart-contract-best-practices
- This repository provides a collection of best practices for developing secure smart contracts, primarily using Solidity. It covers security recommendations, common attack vectors, software engineering principles applied to smart contracts, token implementation advice, and security tools.
Understand Front-Running (Transaction-Ordering Dependence)
masterFront-running exploits the fact that transaction order within a block can be influenced. Because transactions sit in the mempool before being confirmed, attackers can observe pending transactions (e.g., on decentralized exchanges) and submit their own transactions with higher fees to ensure they are processed first. Mitigation strategies include using batch auctions or pre-commit schemes.Explore smart contract security categories
masterDevelopment recommendations in this repository are organized into six distinct categories to help you navigate security best practices:
- General: Guiding principles for the development process.
- Precautions: Principles to prevent attacks or mitigate damage in worst-case scenarios.
- Solidity-specific: Tips and quirks specifically for building smart contracts in Solidity.
- Token-specific: Recommendations for implementing or interacting with tokens.
- Documentation: Guidelines for documenting smart contracts and surrounding processes.
- Deprecated: Vulnerabilities that are largely considered obsolete in modern development environments.
Explore Security Tooling Categories
masterThe Diligence Security Tooling Guide categorizes tools into several functional areas to help developers detect vulnerabilities and maintain high code quality. You can explore specific tools by navigating to their respective categories:
- Visualization: Tools for visualizing EVM bytecode, smart contracts, and control flow graphs.
- Static and Dynamic Analysis: Tools using program analysis to find vulnerabilities and weaknesses.
- Classification: Resources for classifying vulnerabilities and weaknesses in smart contracts.
- Testing: Tools for running, measuring, and managing smart contract tests.
- Linters and Formatters: Tools to highlight code smells and enforce formatting standards.
- Disassemblers and Decompilers: Tools that translate smart contract bytecode into opcodes and Solidity code.
- Formal and Runtime Verification: Tools using verification techniques to detect behavior satisfying or violating invariants.
Access the Smart Contract Security Best Practices documentation
masterThe primary documentation site is hosted online. You can also access the documentation in Chinese or Vietnamese via the provided GitHub links.Note on project maintenance and recommended alternative
masterThis resource is no longer actively maintained. For the most up-to-date and regularly curated security information, it is recommended to use the Smart Contract Security Field Guide.Understand Force-Feeding techniques
masterForce-feeding refers to methods used to send Ether to a contract that bypass the standard Solidity
receive()andfallback()execution flow. Even if these functions containrevert(), the following methods can still force Ether into the contract:- Selfdestruct: Calling the
SELFDESTRUCTopcode sends funds to a target address at the EVM level, bypassing all Solidity-level logic. - Pre-calculated Deployments: Attackers can calculate the address of a contract before it is deployed and send funds to that address in advance.
- Block Rewards and Coinbase: Miners can set the target address of block rewards to a specific contract address (the
coinbase), adding funds via the EVM-level reward mechanism.
- Selfdestruct: Calling the
Understand the core philosophy of smart contract security
masterDeveloping smart contracts requires a unique engineering mindset due to the high cost of errors and the difficulty of patching live code. Follow these core principles:
- Prepare for errors: Implement "circuit breakers" to stop contracts when errors occur, manage fund risk by limiting transfer rates/amounts, and ensure effective paths for bug fixes.
- Cautious rollout: Perform thorough testing, test against new attack vectors even for deployed contracts, start with alpha versions on testnets, and provide bug bounties.
- Maintain simplicity: Keep logic simple, use modular contracts/functions, use widely-used tools (e.g., don't write your own random number generator), and prioritize clarity over performance.
- Stay updated: Monitor new vulnerabilities, update libraries/tools promptly, and use the latest security techniques.
- Understand blockchain specifics: Be wary of external calls (which can execute malicious code), remember that
publicfunctions are accessible to anyone, and note thatprivatedata is still visible on-chain. Always account for Gas costs and block Gas limits.
Understand Block Stuffing DoS Attacks
masterBlock stuffing is a network-level DoS attack where an attacker submits multiple computationally intensive transactions with high gas prices. This consumes the entire Block Gas Limit, preventing other transactions (including those to specific contracts) from being included in the block.
This attack is typically used to block time-sensitive actions, such as preventing a user from interacting with a contract before a timer expires. Contracts that rely on specific timing for payouts or state changes are particularly vulnerable if the potential reward for the attacker outweighs the cost of stuffing the blocks.
Understand fundamental blockchain pitfalls in smart contract development
masterWhen developing for Ethereum, be aware of these core blockchain properties and potential security pitfalls:
- External Contract Calls: Be extremely careful with calls to external contracts, as they can execute malicious code and alter your contract's control flow.
- Public Visibility: All public functions can be called maliciously and in any order. Additionally, all private data in smart contracts is viewable by anyone on the blockchain.
- Gas Constraints: Always account for gas costs and the block gas limit during development.
- Timestamp Imprecision: Timestamps are not perfectly precise; miners can influence the execution time of a transaction within a margin of several seconds.
- Randomness Limitations: Generating true randomness is non-trivial on a blockchain; most common approaches to random number generation are susceptible to being 'gamed' by participants.
Prevent Reentrancy on a single function
masterReentrancy occurs when an external call allows a caller to re-enter the contract and execute logic before the initial function execution completes. To prevent this, follow the Checks-Effects-Interactions pattern: always update the contract's internal state (e.g., zeroing out balances) before making an external call.
// SECURE mapping (address => uint) private userBalances; function withdrawBalance() public { uint amountToWithdraw = userBalances[msg.sender]; userBalances[msg.sender] = 0; // Update state BEFORE the external call (bool success, ) = msg.sender.call.value(amountToWithdraw)(""); require(success); }Handle Ether transfers safely using send, transfer, or call
masterWhen transferring Ether, choose the method based on your security requirements:
transfer()orsend(): Use these to prevent reentrancy. They provide a fixed gas stipend (2,300 gas), which is enough for logging an event but not for complex logic.transfer()is generally preferred oversend().call.value()(): This forwards all remaining gas to the recipient. It is highly flexible but extremely dangerous as it makes the contract vulnerable to reentrancy attacks.
Recommendation: Use a push and pull mechanism. Use
send()ortransfer()for pushing payments to known, trusted addresses, and usecall.value()()for the pull part where users initiate their own withdrawals.