Thank you for joining this three-part series on smart contract development and security. In part one, I reviewed smart contract basics and in part two, I discussed smart contract design and standards. Closing the book on the smart contract journey, the third and final post in this series discusses security, testing and deployment practices, and how web3 properties take existing devops paradigm and shift further to the left.
Iterative design meets immutability: upgrading our release process
Those in the software and technology industry will be familiar with the oft-repeated sentiment of “move fast and break things.” Over the past decade and a half, the tech field and those building on cloud services have benefited from this mantra, thanks to official devops practices, formal CI/CD pipelines and implementation strategies.
- Developers build a proof-of-concept for implementing a new technology, validate use cases and deploy it quickly.
- Breaking changes in this context are easily remedied, at worst, by a hotfix.
- Other less urgent (but still important) changes can be added to the backlog, picked up by members of the software engineering team, and added to the next release.
- This process enables a continuous development flow that allows for fast execution and remediation of issues in production.
However, this traditional devops and CI/CD cycle is interrupted with on-chain properties such as smart contracts and dApps.
- Smart contracts deployed on the Ethereum Virtual Machine (EVM) are immutable by nature.
- Once we deploy a contract/dApp to mainnet, the code deployed is effectively permanent, and only changes permitted by the contract’s existing code and methods can occur.
- This immutability is what enables smart contracts to truly be trustless, but it is also a double-edged sword.
- Any bugs in the published code become a permanent fixture of the smart contract.
Although there is a concept of an upgradeable contract (including some widely adopted standards that have received extensive security review and testing), this is only half the battle. If a critical bug makes it into production, it’s possible that by the time it’s flagged, a devastating exploit has already occurred. Therefore, it is essential to consider design, testing and review in smart contract and dApp development. Here is a break-down of the crucial stages:
Smart contract design:
When designing a smart contract, consider the following questions:
- Has this use case been done before?
- Are there smart contract standards for this use case, or do smart contract libraries exist?
- If none exist, are there other standards or smart contract libraries that have similar designs or goals?
Use the information gathered here to inform the contract design and determine how much of it will be custom versus how much can be pulled from existing smart contract libraries and related projects.
Smart contract development and tooling:
After exploring smart contract design precedents and available templates, find the closest smart contract match from a smart contract library and build it out from there. When it comes to access control and other patterns, it is best to reference existing, reviewed contracts instead of building them from scratch. These tools may be helpful during the development process, when building and testing a contract’s design and functionality:
- Remix (ad-hoc tests)
Note that a solid foundation will go a long way toward securing our contract from the outset. Tooling such as mythril and slither will also aid in getting early feedback on smart contract antipatterns and other identifiable bugs.
Testing and smart contracts:
In modern software development, software test and QA has become an afterthought for some organizations. Sometimes there is a dedicated test team, other times responsibilities are shifted to developers to automate tests as they write features, and some software receives little if any testing or test coverage before being deployed. As stated earlier, smart contracts by nature are immutable once deployed. This means that extra diligence is required when testing and writing tests for smart contracts.
Smart contracts can appear to be deceptively simple at times. The bottom line: it is important to understand that even relative simplicity in smart contract design should translate to just as much effort (or more) around testing the contract. Test driven development can prove especially helpful in encouraging test coverage of smart contracts, throughout the entire development process.
Once tests have been written and established for the smart contract/dApp, the tests should also be set up in the CI/CD pipeline for the repos where the smart contracts are managed and deployed from.
Smart contract review should be ongoing throughout the development and testing process. Reviews prior to each pull request help enforce feedback in a continuous manner. A final detailed security review should be conducted after test coverage is satisfactory, as test findings typically require further code changes or additions.
A smart contract review typically entails:
- Leveraging static analysis and other scanning tools, such as Mythril, Slither, etc.
- Including a manual line-by-line review of the entirety of each smart contract.
- Checks here should include but are not limited to:
- Price Oracle dependencies/manipulation
- Privilege escalation (including missing requires, modifiers, etc.)
- Logic errors/erroneous accounting
- Atomicity violations
- Reentrancy attacks
- Integer overflow and underflow
- Local or devnet deployment (project depending) to test functions of the contract in the wild, including offensive pen testing to attempt to attack any potential weak points in the contract.
- Upgradeability of the contract. If the contract is upgradeable, it is essential to understand who can deploy an update and how it can be done.
- Integration points: consider if the contract can execute arbitrary function commands from specific accounts externally (think keys managed on a centralized exchange). How are these accounts managed off-chain (ie, traditional infrastructure)?
Best practices for reviewing smart contracts
Some of the considerations above include off-chain (i.e., traditional server and SaaS infrastructure) components that are not directly in the smart contract itself. This is important because often a “valid use case” codified in the contract can be exploited if one of the integration points is ill-prepared to weather an attack. As such, it is crucial to consider the full application surface area during the smart contract review, including off-chain components, in addition to the on-chain components.
Observations should be well-documented and backed up with relevant best practices, standards, and external documentation. Likewise, if an anti-pattern is discovered for which a reviewed, open-source smart contract design exists, the reviewed, open-source contract components should be preferred when possible. As companies build out a larger collection of reviewed and hardened smart contracts, it is acceptable and recommended to include these in design considerations for future contracts.
After addressing all issues found during the review, it is important to re-test and review the fixes before promoting the smart contract to testnet and mainnet. Remember, except when utilizing an upgradeable framework, a contract is not editable once deployed.
I hope this series has been helpful in thinking about development and security considerations for smart contracts in the blockchain and decentralized space.
Read the results of our new Global IT Executive Survey: The Innovation vs. Technical Debt Tug-of-War.