Yos Riady optimize for learning

Best Practices for Smart Contract Development

Smart Contract Development Best Practices

Software development has been around for decades. We benefit from the best practices, design patterns, and nuggets of wisdom that has accumulated over that long history.

In contrast, smart contract development is just getting started. Ethereum and Solidity were released only a few years ago in 2015. There’s no definitive stack of tools - like LAMP or MEAN in web development - to build decentralized apps. There are no developer handbooks like Design Patterns or Clean Code for smart contracts. Information is scattered all over the place.

The crypto space is mostly uncharted territory. To address this, I’m writing the missing guide I wish existed. It summarizes the lessons I’ve learned from writing smart contracts, building decentralized applications, and studying open source projects in the Ethereum ecosystem. My hope is it will be useful to developers new to crypto.

Let’s get started!

💡 This handbook is a living document. If you have any feedback or suggestions, feel free to comment or email me directly. Thanks!

Who this is for

This handbook is for:

  • Developers who are just starting out with smart contracts, and
  • Experienced Solidity developers who wish to bring their work to the next level.

This is NOT meant to be an introduction to the Solidity language.

TL;DR

Use a Development Environment

Use a development environment such as Truffle (alternatively, Embark, Buidler dapp.tools) to improve your day-to-day productivity.

Truffle framework

These environments improves developer experience by helping you manage and automate recurring tasks which in smart contract development. This includes compiling contracts, running unit tests, and deploying contracts.

Truffle built-in commands

For example, Truffle provides the following useful commands out-of-the-box:

  • compile: Compiles a Solidity contract to its ABI and bytecode formats.
  • console: Instantiates an interactive JS console where you can call and interact with your web3 contracts.
  • test: Runs your contracts’ unit test suite.
  • migrate: Deploys your contracts as a set of migrations.

You can be extend the functionality of these tools via plugins (such as truffle-security and truffle-plugin-verify.) For bespoke needs, custom plugins are an option.

Develop Locally

Run a local blockchain with Ganache (or Ganache CLI) to speed up your iteration cycle.

You can run Ganache locally to speed up development.

On the mainnet, Ethereum transactions takes time to get mined. You can skip all this idle time by developing on a local chain. Instead of paying gas (Ethereum transactions cost money) and waiting for blocks to get mined, you can run your contracts locally to have transactions instantly mined.

Ganache block explorer

Ganache comes with a built-in block explorer that shows your decoded transactions, contracts, and events. This local environment is configurable to suit your testing needs.

Use Static Analysis Tools

Static analysis or ‘linting’ is the process of running a program that analyzes code for programming errors. In smart contract development, this is useful for catching style inconsistencies and vulnerable constructs that the compiler may have missed.

Linting Solidity code with Solhint

Lint Solidity code with solhint and Ethlint. Solidity linters are similar to linters for other languages (e.g. JSLint), except they pay special attention to Security validations.

Security-specific tools identify smart contract vulnerabilities. Examples include truffle-security (MythX), Mythril, Slither, and Manticore. These tools run a suite of vulnerability detectors and prints out a summary of any issues found. Developers can use this information to find and address vulnerabilities throughout the implementation phase.

Use Pre-Commit Hooks

Make your developer experience seamless by setting up Git hooks with husky. Pre-commit hooks let you run your linters before every commit. For example:

// package.json
{
  "scripts": {
    "lint": "./node_modules/.bin/solhint -f table contracts/**/*.sol",
  },
  "husky": {
    "hooks": {
      "pre-commit": "npm run lint"
    }
  },
}

The above snippet runs a predefined lint task before every commit, failing if there are outstanding style or security violations in your code. This setup enables developers in your team to work with the linter in an iterative approach.

💡 You can go one step further and set up a CI pipeline.

Understand Security Vulnerabilities

It’s hard to write software without any bugs. Defensive programming techniques can only go so far. Fortunately, you can fix bugs by deploying a hotfix. With continuous delivery, traditional software patches are frequent and straightforward.

However, smart contracts are immutable. It’s hard and sometimes impossible to upgrade contracts that are already live. In this aspect, smart contracts are closer to virtual hardware development than software development.

Worse, bugs can lead to extreme financial losses. The DAO Hack lost more than 11.5 million Ether (US$70M at the time of the hack, now over $2B) and the 2nd Parity Hack lost US$200M of user funds.

Smart contract development demands a completely different mentality when it comes to shipping. ‘Move fast and break things’ does not apply here. You need huge amounts of rigor and intensity to make software that can’t be hacked and performs as expected. Because of the above constraints, smart contract developers must put in an exceptional amount of effort upfront. This requires developers to:

  1. Be familiar with Solidity security vulnerabilities, and

  2. Understand Solidity design patterns such as pull vs push payments and Checks-Effects-Interactions, amongst others.

💡 Tip for Beginners: You can practice your Solidity security chops in an interactive way with Ethernauts.

Write Unit Tests

Uncover bugs and unexpected behaviour early with a comprehensive test suite. Writing scenarios of different user flows through your protocol helps to validate that your contracts performs as designed.

Truffle uses the Mocha testing framework and Chai for assertions. You write unit tests in Javascript against contract wrappers exactly like how frontend ÐApps call your contracts.

From Truffle v5.1.0 onwards, you can interrupt tests to debug the test flow and start the debugger, allowing you to set breakpoints, inspect Solidity variables, etc.

Truffle is missing several features which are essential for testing smart contracts. Installing openzeppelin-test-helpers gives you access many important utilities for validating contract state, such as matching contract events and moving forward in time.

Alternatively, OpenZeppelin Test Environment offers a tooling-agnostic option if you prefer using other test runners.

Measure Test Coverage

Writing tests is not enough; Your test suite must reliably catch regressions. Test Coverage is one of the earliest techniques to measure the effectiveness of your tests.

A program with high test coverage has had more of its source code executed during testing, which suggests it has a lower chance of containing undetected software bugs compared to a program with low test coverage. Untested code could do anything!

You can collect Solidity code coverage with solidity-coverage.

Set up Continuous Integration

Once you have a test suite, you’ll want to run it as frequently as possible to catch potential bugs. There are a few ways to accomplish this:

  1. Set up Git hooks as we did earlier for linting, or
  2. Set up a CI pipeline that executes your tests after every Git push.

If you’re looking for an out-of-the-box CI check out Truffle Teams or Superblocks. They provide hosted environments for continuous smart contract testing.

With minimal configuration required, this service tests your code in every commit for maximum confidence. You can also monitor your deployed contracts’ transactions, state, and events.

Security Audit Your Contracts

Security audits help you uncover unknowns in your system that defensive programming techniques (linting, unit tests, design patterns) miss.

Audits help uncover the unknown.

You can perform penetration testing with the security tools mentioned in an earlier section. In this exploratory phase, you try your best to break your contracts - supplying unexpected inputs, calling functions as different roles, etc. At this stage, you should not find significant bugs if you have comprehensive unit tests.

Bring in External Auditors

Major protocols in the Ethereum space go one step further by hiring external security auditors who dive deep into their codebase to find potential security holes. These auditors use a combination of proprietary and open-source static analysis tools such as:

  • Manticore, a symbolic emulator capable of simulating complex multi-contract and multi-transaction attacks against EVM bytecode.
  • Ethersplay, a graphical EVM disassembler capable of method recovery, dynamic jump computation, source code matching, and binary diffing.
  • Slither, a static analyzer that detects common mistakes such as bugs in reentrancy, constructors, method access, and more.
  • Echidna, a next-generation smart fuzzer that targets EVM bytecode.

Auditors will help identify any design and architecture-level risks and educate your team on common smart contract vulnerabilities.

At the end of the process, you get a report that summarizes the auditors’ findings and recommended mitigations. You can read audit reports by ChainSecurity, OpenZeppelin, Consensys Diligence, and TrailOfBits to learn what kind of issues are found during a security audit.

Use Audited, Open Source Contracts

Secure your code from Day 1 by using battle-tested open-source code that has already passed security audits. Using audited code reduces the surface area you need to audit later on.

OpenZeppelin Contracts is a framework of modular, reusable smart contracts written in Solidity. It includes implementations of popular ERC standards such as ERC20 and ERC721 tokens. It comes with the following out of the box:

  • Access Control: Contract ownership with Ownable and and Role-Based Access Control with Roles.
  • ERC20 & ER721 Tokens: Open source implementations of popular token standards, along with optional modules.
  • Gas Stations Network: Improves usability allowing you to build apps where you pay for your users transactions.
  • Utilities: SafeMath, ECDSA, Escrow, and other utility contracts.

You can deploy these contracts as-is or extend it to suit your needs as part of a larger decentralized system.

💡 Tip for Beginners: Open source Solidity projects such as OpenZeppelin Contracts are excellent learning materials for new developers. They provide a readable introduction to what’s possible with smart contracts. Don’t hesitate to check it out!

Hold a Public Testnet

Before you launch your protocol on the Ethereum mainnet, consider launching on a testnet. Think of it as deploying to staging before production. Testnets such as Rinkeby and Kovan have faster block times and test Ether can be requested for free.

During a testnet phase, you can organize a bug bounty program. Your users and the larger Ethereum security community can help you identify any remaining critical flaws in the protocol, in return for a monetary reward.

Consider Formal Verification

A precise specification is required to find bugs in a software design.

Formal verification is the act of proving or disproving the correctness of an algorithm against a formal specification, using formal methods of mathematics. The verification of these systems is done by providing a formal proof on an abstract mathematical model of the system, such as finite state machines and labelled transitions.

TLA+ Snippet

The reason why formal verification hasn’t caught on is because of a reputation of requiring a huge amount of effort to verify a tiny piece of relatively straightforward code. The return on investment is only justified in safety-critical domains such as medical systems and avionics. If you’re not writing code for medical devices or rockets, you tolerate bugs and fix iteratively.

Recently, teams at Amazon Web Services (AWS) have used formal verification with Leslie Lamport’s TLA+ to verify the correctness of S3 and DynamoDB:

“In every case TLA+ has added significant value, either finding subtle bugs that we are sure we would not have found by other means, or giving us enough understanding and confidence to make aggressive performance optimizations without sacrificing correctness.”

Smart contract development requires a complete shift in mindset. You need huge amounts of rigor and intensity to make software that cannot be hacked and will perform as expected. Given the constraints of smart contracts, the decision to go for formal verification may be a prudent one. After all, you only have one chance to get it right.

Within the Ethereum ecosystem, available model checkers include:

  • VerX is an automated verifier for custom function requirements of Ethereum contracts. VerX takes as inputs Solidity code and functional requirements written in VerX’s specification language, and either verifies that the property holds or outputs a sequence of transactions that may result in violating the property.

  • cadCAD is a Python package that assists in the processes of designing, testing and validating complex systems through simulation, with support for Monte Carlo methods, A/B testing and parameter sweeping. It’s been used to simulate cryptoeconomic models in the Clovers project.

  • KLab is a tool for generating and debugging proofs in the K Framework, tailored for the formal verification of ethereum smart contracts. It includes a succinct specification language for expressing the behavior of ethereum contracts, and an interactive debugger.

For reference, you can see example results of formal verification here.

Store Keys in a Secure Manner

Store private keys of Ethereum accounts in a secure manner. Here are a few suggestions:

  • Generate entropy safely.
  • Do NOT post or send your seed phrase anywhere. If it’s a must, use an encrypted communication channel such as Keybase Chat.
  • Do use hardware wallets such as a Ledger.
  • Do use a multi-signature wallet (Gnosis Safe) for particularly sensitive accounts.

💡 With the rise of smart contract wallets, seed phrases may become less prevalent over time.

Make It Open Source

Smart contracts enable permissionless innovation that lets anyone build and innovate on them. That is what blockchains are really useful for. Public, programmable and verifiable computation.

If you’re building a composable protocol, your main audience is other developers. To attract developers you need to show that you won’t change the rules of the game later on.

Etherscan-Verified Code

Making your code public allows anyone to fork your code should things go awry. You should also verify your contracts on Etherscan.

Prioritize Developer Experience

Integrating payments prior to Stripe was hard. Early payments companies lacked modern code bases, and things like APIs, client libraries and documentation were virtually non-existent. Stripe made it easy for developers to add payments to their software.

Stripe Developer Experience

Likewise, the developer experience of your protocol is paramount. Make it easy for other developers to build on your protocol by creating developer-friendly APIs. Here are some suggestions:

The user experience of your developer portal, the completeness of the API documentation, the ease with which people can search for the right solution for their use case, and the speed at which developers can start calling your contracts are all critical for adoption to happen.

💡 The 0x protocol is probably the gold standard when it comes to developer experience. Their high adoption rate is testament to the protocol’s value and smooth onboarding.

Provide Contract SDKs

While writing and maintaining robust, client libraries for many programming languages is non-trivial, having these readily available can be very helpful for the majority of developers who want to use your protocol.

Contract wrappers built with typechain, truffle-contract (or web3.js) makes calling contracts as simple as calling Javascript functions. You can then distribute these libraries as packages on NPM for developers to install.

var provider = new Web3.providers.HttpProvider("http://localhost:8545");
var contract = require("truffle-contract");

var MyContract = contract({
  abi: ...,
  unlinked_binary: ...,
  address: ..., // optional
  // many more
})
MyContract.setProvider(provider);

const deployed = await MyContract.deployed();
const result = await instance.someFunction(5);

💡 Having a client SDK greatly reduces the effort required for developers to get started, especially for those new to to a specific programming language.

Some projects go one step further and provide fully-functional codebases that you can run and deploy. For example, the 0x Launch Kit provides decentralized exchanges that works out-of-the-box.

Write Good Documentation

Building on open source software reduces development time, but it comes with a tradeoff: learning how to use that software takes time. Good documentation reduces the time spent learning how to use your protocol.

There are many types of documentation:

  • High-level explainers describe in plain-English what your protocol does. Explain in clear terms what the capabilities of your protocol. This section enables decision makers to evaluate whether or not your product serves their use cases.
  • Tutorials go into more details: step-by-step instructions and explanations of what the various components are and how to manipulate them to achieve a certain goal. Tutorials should strive to be clear, concise and evenly spaced across steps. Use plenty of code examples to encourage copy/pasting.
  • API Reference document the technical details of your smart contracts, functions, and parameters.

Generate an API reference with solidity-docgen. This tool allows you to document your code inline using NatSpec comments (similar to Javadoc and JSDoc) and then produce a website to publish the documentation.

If you have an HTTP API, check out slate.

Build CLI Tools and Runbooks

Build internal CLI tools and runbooks to improve operations.

Runbooks are codified procedures to achieve a specific outcome. Runbooks should contain the minimum information necessary to successfully perform the procedure. With smart contracts, this is usually a script containing one or more contract calls.

💡 Start with an effective manual process, implement it in code, and trigger automated execution where appropriate.

Should things go wrong, runbooks provide developers who are unfamiliar with procedures or the workload, the instructions necessary to successfully complete an activity such as a recovery action. The process of writing runbooks also prepares you to handle potential failure modes. Perform internal exercises to identify potential sources of failure so that they can be removed or mitigated.

Set up Event Monitoring

Efficient and effective management of contract events is necessary for operational excellence. An event monitoring system for your smart contracts keeps you notified of real-time changes in the system.

You can roll out your own monitoring backend with web3.js or use a dedicated service such as Dagger, Terminal, Blocknative Notify or Tenderly.

On Building DApp Backends

DApps need a way to get and transform data from underlying data sources. However, data in Ethereum are not stored in a read-optimized format. Reading contract data directly from an Ethereum node is too slow for user-facing web and mobile apps. You need to process and index raw contract data into a friendlier format to improve response times.

theGraph API explorer

You can build your own indexing service, and you’d have to run and operate it on centralized infrastructure. This backend would communicate with an Ethereum node and subscribe to relevant contract events, perform data transformations, and persist the result in a read-optimized datastore. There are open source implementations you can use as reference if you decide to roll your own.

Alternatively, theGraph offers a way to create a decentralized GraphQL index of your smart contracts. Queries are processed on a decentralized network that ensures that data remains open and that DApps continue to run no matter what.

💡 The lack of regulatory clarity in jurisdictions across the world means that at the flip of a hat, control can become liability. To address this, making parts of your system decentralized and non-custodial may help reduce that liability.

On Building DApp Frontends

A frontend application allows users to interact with smart contracts. Examples include the Augur and Compound apps. DApp frontends are usually hosted in a centralized server, but can also be hosted on the decentralized IPFS network to further introduce decentralization and reduce liability.

These frontend applications retrieve smart contract data from an Ethereum node with Ethereum clients such as web3.js, Drizzle, or ethers.js. For better performance, frontends can call a backend application which indexes smart contract events into a read-optimized format.

There are several DApp boilerplates available, such as the OpenZeppelin Starter Kit and Truffle boxes. They come with everything you need to start using smart contracts from a React app.

Strive for Usability

Crypto has a usability problem. Gas fees and seed phrases are intimidating technical quirks to a mainstream audience. Fortunately, there are active community efforts towards improving the crypto user experience.

Meta Transactions and the Gas Stations Network offers a solution to the gas fee problem. Meta transactions allow services to pay the gas fees on behalf of their users, skipping the need for users to hold Ether in their wallet. It also lets users pay fees in other tokens instead of ETH. Meta transactions are made possible through clever use of cryptographic signatures.

Hosted wallets and wallet protocols remove the need for browser extensions and seed phrases. Projects under this category include Fortmatic, Portis, Bitski, SquareLink, Universal Login, Torus, Argent, and walletconnect.

💡 Because the wallet market is very fragmented, consider using an aggregator like web3connect to support all major wallets.

To drive mass user adoption, usability is key. Be sure to keep these projects in mind.

Build with Other Protocols in Mind

A digital finance stack has emerged in Ethereum. Protocols are building on top of each other, powered by the permissionless and composable nature of smart contracts. These protocols include:

  • MakerDAO: Digitally-native stablecoin, Dai.
  • Compound: Digitally-native autonomous asset lending and borrowing.
  • Uniswap: Digitally-native autonomous asset exchange.
  • Augur: Digitally-native prediction market.
  • DYDX: Algorithmically-managed derivative market.
  • UMA: Synthetic asset platform.
  • And many more…

Each protocol provides the foundation and the stability for the protocols on top of it to reach higher levels of abstraction. Unlike building on centralized platforms, access to these innovations cannot be taken away from under you.

Ethereum Money Legos

If Ethereum is the Internet of Money, Decentralized Finance protocols are Money Legos. Each financial building block opens the door to new things that can be built on Ethereum. As the number of Money Legos grows, so too does the number of novel financial products. We’ve only begun scratching the surface of what’s possible.

💡 You can experience the lightspeed pace of innovation in the DeFi space by just looking at the varieties of DAI.

Don’t reinvent the wheel in isolation. Build with other protocols in mind. Instead of forking a clone of an existing protocol, can you build something for or with the pieces that already exist?

This philosophy extends to centralized services. There’s a growing ecosystem of third-party services that are worth looking into for your decentralized application:

  • Infura, Azure Blockchain, QuikNode, Nodesmith: Hosted Ethereum nodes save you the headache of running your own.
  • 3box: Decentralized storage and social API for comments and user profiles.
  • Starkware: Zero knowledge proofs offers scalability and privacy solutions.
  • Matic: Faster and extremely low-cost transactions.

This ever growing set of building blocks gives developers new options to ship better DApps, faster.

Understand Systemic Protocol Risks

Everyone should always be aware that there is a possibility of a smart contract flaw being found in any of the decentralized finance protocols. Because of the permissionless composability of Ethereum money legos, a single black swan event in a single protocol can cascade throughout the stack.

Ethereum: The Digital Finance Stack

The DeFi Score offers a way to quantify this risk. The likelihood becomes lower depending on whether the associated smart contracts have been audited, how long the protocol has been in use, the amount of funds that has been managed by the protocol so far, etc.

Unfortunately, similar to how SLA scores are calculated, this risk compounds as more protocols are composed together. Some argue that DeFi is a house of cards waiting to topple, given current oracle vulnerabilities and emergency hatches on some protocols. You can minimize this risk by purchasing smart contract insurance and interacting only with audited smart contracts.

Participate in dev communities

Smart contract development and decentralized application engineering are fields undergoing rapid innovation. To keep up with the latest developments in the space, be sure to join online developer communities where most of these discussions take place.

Ethereum communities include ETH Research, Ethereum Magicians, r/ethdev, OpenZeppelin Forum, and the EIPs Github repo.

In Closing

Thank you for reading. This handbook is a living document. As the Ethereum developer ecosystem grows and evolves, new tools will emerge and old techniques may become obsolete.

If you have any feedback or suggestions, feel free to comment or email me directly. Thanks.

Author

Yos is a software craftsman based in Singapore. Learn more.

📬 Get updates straight to your inbox!

Subscribe to my newsletter to make sure you don't miss anything.




Here's something you might be interested in...

Going Serverless book

Have you heard about the Serverless programming model? The Going Serverless book teaches you how to build scalable applications with the Serverless framework and AWS Lambda. You'll learn how to design, develop, test, deploy, and secure Serverless applications from planning to production.

Find out more →