Layer 2: Smart Contracts Development Best Practices

Explore the main blockchain-specific development patterns

TriWei.io
7 min readJan 14, 2024

This article provides an insightful look into the critical techniques and strategies for building secure and robust smart contracts in the Web3 environment. It focuses on best practices that are fundamental to creating stable and secure blockchain applications, addressing the unique challenges and nuances of smart contract development. Ideal for developers and project managers in the Web3 space, this guide is an invaluable resource for anyone looking to enhance the security and efficiency of their blockchain projects.

Further insights into TriWei’s security approach can be explored at triwei.io/education.

1. KISS Principle: Keep It Simple, Stupid

The KISS (Keep It Simple, Stupid) principle is a foundational concept that holds even more weight in the world of Web3. Unlike common web2 applications, the repercussions of bugs in smart contracts can be severe. A complex contract introduces numerous points of potential failure, increasing the likelihood of vulnerabilities. Simplicity, therefore, is your best ally in the world of smart contracts.

Striving for simplicity means minimizing the complexity of your contract logic. This not only reduces the attack surface but also makes auditing your code more manageable. Auditors can more effectively review concise code, reducing the likelihood of critical issues slipping through the cracks. Furthermore, simpler code is easier to maintain and upgrade, ensuring long-term security.

The complexity in smart contracts should be proportional to the necessity. On a blockchain where transaction costs are minimal, implementing every aspect in assembly language might be excessive. In contrast, on Ethereum, where transactions are more expensive, employing assembly language can be advantageous, but it requires meticulous attention to detail and careful execution to avoid costly errors.

Adopt a Test Driven Development Approach

When tests are written after development, there is a tendency for tests to be biased towards the already written code, potentially overlooking edge cases or incorrect behaviors. By writing tests first, developers ensure that they are not just testing to confirm that the code does what it was written to do, but rather, testing to ensure that the code meets the originally defined requirements.

Before writing any code, developers should define what the smart contract is supposed to achieve. This involves detailing its functionalities, desired behaviors, and the various conditions it needs to handle. Once the requirements are well understood, the next step is to write comprehensive tests based on these requirements. These tests are designed to fail initially, as they are written before the actual smart contract code is developed. The purpose of writing tests first is to provide a clear and objective target for what the code needs to accomplish.

2. Design Proper Access Controls

Web3 projects should adhere to the principle of Least Privilege and adopt a Fine-Grained Role-Based Access Control system. Relying on a single owner for administrative control creates a single point of failure that malicious actors may exploit. Instead, design multiple roles for various administrative activities within your project. Each of these roles should then be carefully managed by role administrators, who are responsible for assigning these roles to specific addresses, thereby decentralizing control and enhancing security.

By implementing role admins who can assign operative roles, you distribute access and control more effectively. This approach not only reduces the risk of unauthorized access but also enhances the accountability and traceability of actions within your project.

3. Use Secure Audited Libraries and Standard Base Contracts

The foundation of secure Web3 development lies in the careful selection of libraries and contracts. Reputable libraries, like those provided by OpenZeppelin, have undergone rigorous audits and testing, significantly reducing the chances of vulnerabilities in your project.

When customization is necessary, extend these libraries instead of modifying them locally. Modifying core components can introduce unforeseen risks and compatibility issues. Leveraging established, audited libraries not only saves time but also instills confidence in the security of your project. Always make sure to extend the right library version, as even the most audited and battle-tested libraries can still come with vulnerabilities.

4. Favor the Pull Over Push Pattern

Adopting the “pull” pattern over the “push” pattern wherever possible is a prudent choice in Web3 development. The push pattern, where external entities can directly trigger actions within your contract, can expand your attack surface. In contrast, the pull pattern allows users to request data or actions, giving you better control and security over your project.

Malicious actors can exploit an insecure push pattern by deliberately causing transactions to fail when they are supposed to receive native Ethers, utilizing a receive() function designed to revert the transaction under certain conditions. Similarly, they can revert NFTs transfer when they are received through the safeTransfer() method. Such actions would lead to a Denial of Service attack on the protocol. Often, these attacks are carried out not just for financial gain but also for the sole purpose of causing disruption or griefing.

5. Follow Ethereum Improvement Proposals (EIPs)

Standards play a pivotal role in the Web3 ecosystem. Sticking to Ethereum Improvement Proposals (EIPs) ensures compatibility and enhances composability with other projects and platforms. Deviating from established standards can lead to compatibility issues and security vulnerabilities down the road.

EIPs provide a standardized framework for smart contract development, helping you maintain a secure and interoperable project. By adhering to these standards, you can also benefit from the collective wisdom and scrutiny of the Web3 community.

6. Validate Input Data

While it may seem trivial, input data validation is a paramount concern in Web3 development. Many hacks and vulnerabilities arise due to inadequate validation of inputs. Ensure that your contracts thoroughly validate and sanitize data inputs to prevent potential exploits.

Incorporate robust input validation checks to verify the integrity and authenticity of data received by your smart contracts. This includes validating user inputs, external data sources, and interactions with other contracts. Comprehensive input validation can significantly reduce the risk of common attack vectors, such as input manipulation and data injection attacks.

7. Implement the Check-Effect-Interaction Pattern

To minimize the attack surface in reentrancy attacks, it is crucial to implement the Check-Effect-Interaction pattern diligently. Even if you are already using a reentrancy lock, adding a second layer of protection through this pattern is essential, since things can go south in countless edge-cases ways — e.g. a Vyper compiler bug in July 2023 messed up the reentrancy lock and lead to multiple Curve.Fi liquidity pools being drained.

The Check-Effect-Interaction pattern involves three distinct steps:

  • Check: Verify preconditions before any state changes occur.
  • Effect: Execute state changes based on the checked preconditions.
  • Interaction: Interact with external contracts and call external functions after the state changes have been executed.

This pattern helps ensure that state changes are isolated from external interactions, reducing the risk of reentrancy vulnerabilities.

8. Properly Use Oracles for Price Fetching

When fetching prices on-chain, exercise caution and avoid relying on decentralized exchanges’ getter functions. Depending solely on these functions makes your project susceptible to flash-loan attacks, where malicious actors can manipulate prices to their advantage.

To fetch prices securely, integrate reputable oracles into your project. Oracles provide reliable and up-to-date price data from external sources. By using oracles, you can ensure that the price information used in your contracts is accurate and tamper-resistant, bolstering the security of your project.

Time-Weighted Average Price (TWAP) oracles offer a compromise between oracles and on-chain price getting, by providing a mechanism to calculate the average price of an asset over a specified time frame. This approach is designed to offer a more stable and reliable price reference by mitigating the impact of short-term price volatility and manipulation commonly seen in cryptocurrency markets. TWAP oracles aggregate price information over time intervals, which can range from minutes to hours, to derive an average price that is less susceptible to sudden market movements. However, TWAP usage faces inherent risks like pricing manipulation in low liquidity pools, time-based tactics by attackers influencing average calculations, and potential block manipulation through coordinated actions.
Chaos Lab, in their research, particularly focuses on these risks within the context of Uniswap V3’s TWAP oracles. They have delved into various potential attack vectors, such as sophisticated pricing manipulation tactics and block-spanning attacks.

9. Style Guide and Documentation

Well-documented code and adherence to a style guide are often underestimated but critical aspects of secure development in Web3.

Proper documentation includes:

  • Functional and Technical Requirements: These are not only meant for end-users but also serve as a vital resource for internal developers and external auditors. Clear requirements help everyone understand the intended functionality and behavior of your project.
  • NatSpec and Comments: Utilize Natural Specification (NatSpec) comments to provide in-depth explanations of the purpose and functioning of your contracts, especially in complex sections. Well-commented code is easier to review, audit, and troubleshoot, contributing to overall project security.

In conclusion, by adhering to these principles you can significantly reduce the vulnerabilities in your project. This not only safeguards your project but also contributes to the overall security and trustworthiness of the Web3 ecosystem.

As we conclude our exploration of the second layer in our seven-part series on Web3 security, remember that this is just the beginning. Each layer offers unique insights and strategies crucial for the security and success of any Web3 project. Continue your journey with us to uncover more essential aspects of Web3 security.

Dive into the remaining layers and enrich your knowledge at triwei.io/education. Stay informed, stay secure, and lead the way in the Web3 space.

--

--

TriWei.io
TriWei.io

Written by TriWei.io

Three experienced solo auditors joined to create TriWei, a Smart Contract auditing firm. Lean process, high quality, competitive pricing. www.triwei.io

No responses yet