Crafting Secure Contracts: Strategies for Blockchain Protocol Safety
Building smart contracts can be challenging, particularly in the current landscape where hacks are occurring at an alarming rate. Securing these contracts is a challenge, but it’s an important one to tackle. This guideline will help you build your contracts with security in mind and make the process for audits much simpler and more efficient.
The absolute first thing to remember when building out your smart contracts is modularity. This means keeping logic separated into different files based on functionality. Keeping logic separate helps not only with readability but also identifies potential issues easier and earlier. Having smart contracts deal with multiple different elements of a system causes confusion and allows vulnerabilities to slide by unnoticed. Modularity also makes the life of your auditor much easier and can increase their productivity time by not having to sift through mixed-up functionality.
The next development advice is to follow patterns. Development patterns are proven methodologies to follow to ensure certain functionality and security. One of the most important patterns is the Check-Effects-Interactions pattern. This pattern requires smart contract logic to first validate the transaction, then apply the effects of the transaction. Think about a scenario where Bob needs to send Alice 20 USDC. Firstly, the contract must validate that Bob actually owns 20 USDC. Once this has been completed, deduct the 20 USDC from Bob’s wallet and increase Alice’s USDC balance by 20. The last part of the transaction will then be the actual transfer of funds from Bob’s wallet to Alice’s. Remember, due to the blockchain's atomic nature, if the transfer fails, the whole transaction will revert, and the balance updates will no longer hold. So you need not worry about that.
The next piece of advice I would like to share is to handle external calls with caution. External calls refer to transactions where your contract calls another smart contract or external source. Most smart contracts are breached through external calls to other contracts where we are unsure of their functionality. Even a simple transfer of ETH to another contract can be intercepted and manipulated through malicious code in the receiving contract. If you are sending funds to a contract in which you are unsure of the functionality, always make sure that your system will not crash if that transfer fails. This can lead to DoS attacks. Follow the pull over push pattern. If you are performing a transaction to a smart contract that is publicly available, it is advisable to read through the contract to ensure there are no hooks or hidden functionalities that could be detrimental to your system.
Don’t reinvent the wheel. Being able to effectively use open source libraries for certain functionality can help reduce the areas where you could possibly make a mistake. It is not cheating to use widely adopted libraries for ease and simplicity. A great example of this would be OpenZeppelin's wide variety of contracts available. Whether you need secure access control or a simple governance module, their contracts have been through multiple rounds of scrutiny to ensure their safety. Using them can help you shift your focus to another area of the protocol and remove some of the security risks. However, remember that although these contracts have been audited and analyzed individually, it is still important to ensure that integration with these libraries does not conflict with any of the functionality intended for your system or introduce other bugs.
Now that you are happy with the development of your contracts and feel that they are in a good place, the next important phase of the smart contract development cycle is testing. Whether you are developing using Hardhat, Truffle, or Foundry, you should always perform unit and integration tests to ensure your contracts are behaving in the manner you expect. Usually, you would start with unit tests. These tests need to be as molecular as possible. Break down and test each line of code. Once you are confident each line performs correctly, try to break the contracts by performing as many integration tests as possible. In other words, write down the different user flows users may have that interact with multiple functions and contracts, and write tests that follow these flows, making sure that each step along the way, the results are as you would expect them to be.
The last step in securing your contracts is performing an external audit. Auditors are the best in the business, and their sole purpose is to break your contracts as badly as they can and report back to you how they broke your functionality as well as how you may protect against it. However, being an auditor is hard enough already, and having good documentation and clean code makes their lives a whole lot easier. When preparing for an audit, write comprehensive notes on how you intend your system to work. Break down the different modules and explain their purpose. Breaking it down further, using NatSpec on your functions helps tremendously. When auditors can see exactly what a function's purpose is, they can interrogate it more efficiently. Preparing correctly for an audit can bring the audit timeline down significantly, inevitably saving you money and making it easier for the auditor to find vulnerabilities.
We truly hope this article will help you ensure safety in your smart contracts. The methods we have covered above are simple yet highly effective ways to combat malicious actors. Being able to keep your contracts modular, following the Check-Effects-Interaction pattern, investigating external calls, using open-source libraries, testing thoroughly, and preparing for an audit will give you a great leap forward to being a secure protocol.