Smart Contract Development Best Practices

The history of software development spans decades. We benefit from the best practices, design patterns, and nuggets of wisdom that has accumulated over half a century.

In contrast, smart contract development is just getting started. Ethereum and Solidity launched in 2015, only a handful of years ago.

The crypto space is an ever-growing uncharted territory. There’s no definitive stack of tools to build decentralized apps. There are no developer handbooks like Design Patterns or Clean Code for smart contracts. Information about tools and best practices are scattered all over the place.

You’re reading the missing guide I wish existed. It summarizes the lessons I’ve learned from writing smart contracts, building decentralized applications, and open source projects in the Ethereum ecosystem.

📬 Get updates straight to your inbox.

Subscribe to my newsletter so you don't miss new content.

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

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.

If you’re a developer new to crypto, please let me know if you find this guide helpful.

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 get productive, fast.

Truffle framework

Using a development environment speeds up recurring tasks such as:

  • Compiling contracts
  • Deploying contracts
  • Debugging contracts
  • Upgrading contracts
  • Running unit tests

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 to a network.

Truffle supports plugins that offer additional features. For example, truffle-security provides smart contract security verification. truffle-plugin-verify publishes your contracts on blockchain explorers. You can also create custom plugins.

Likewise, Buidler supports a growing list of plugins for Ethereum smart contract developers.

Whichever development environment you use, picking a good set of tools is a must.

Develop Locally

Run a local blockchain for development 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 cost money and can take minutes to be confirmed. Skip all this waiting by using a local chain. Run your contracts locally to get free and instant transactions.

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.

Setting up is easy and quick. Download here.

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 code that the compiler may have missed.

1. Linters

Linting Solidity code with Solhint

Lint Solidity code with solhint and Ethlint. Solidity linters are similar to linters for other languages (e.g. JSLint.) They provide both Security and Style Guide validations.

2. Security Analysis

Plugins

Security analysis tools identify smart contract vulnerabilities. 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.

Options include: Mythril · Slither · Manticore · MythX · Echidna · Oyente

Extra: 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.

Understand Security Vulnerabilities

Write software without bugs is notoriously difficult. Defensive programming techniques can only go so far. Fortunately, you can fix bugs by deploying new code. Patches in traditional software development are frequent and straightforward.

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

Worse, smart contract 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. Today, with a market size of nearly $1B, the DeFi space has a lot to lose - should things go wrong.

Smart contract development demands a completely different mentality than web development. ‘Move fast and break things’ does not apply here. You need to invest lots of resources upfront to write bug-free software. As a developer, you must:

  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.

  3. Use defensive programming techniques: static analysis and unit tests.

  4. Audit your code.

The following sections will explain points (3) and (4) in detail.

💡 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. Testing different scenarios through your protocol helps you identify edge cases.

Truffle uses the Mocha testing framework and Chai for assertions. You write unit tests in Javascript against contract wrappers like how frontend ÐApps will 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 measures the effectiveness of your tests.

A program with high test coverage has more of its code executed during testing. This means it has a lower chance of having undetected bugs compared to code with low 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, run it as frequently as possible. 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.

Hosted CIs run your unit tests regularly 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.

In this exploratory phase, you try your best to break your contracts - supplying unexpected inputs, calling functions as different roles, etc.

Nothing can replace manual security audits, especially when the surface area of a hack can be the entire DeFi ecosystem.

⚠️ Before proceeding to the next phase, your code should already pass the security tools mentioned in an earlier section.

Bring in External Auditors

Major protocols in the Ethereum space hire (expensive) 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: Who’s allowed to do what.
  • ERC20 & ER721 Tokens: Open source implementations of popular token standards, along with optional modules.
  • Gas Stations Network: Abstracts away gas from your users.
  • 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 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! Start here.

Launch on 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. Rinkeby and Kovan testnets have faster block times than mainnet and test Ether can be requested for free.

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

Consider Formal Verification

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 is done by providing a formal proof on a 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.

However, 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 justified. After all, you only have one chance to get it right.

VerX

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.

Gnosis Safe

💡 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 DeFi protocol, you want to attract third-party developers. To attract developers you need to show that you won’t change the rules of the game later on. Open sourcing your code inspires confidence.

Etherscan-Verified Code

Making your code public also allows anyone to fork your code should things go awry.

💡 Remember to verify your contracts on Etherscan.

Prioritize Developer Experience

For the longest time, integrating payments was really 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. They are now incredibly successful.

The developer experience (DevEx) of your protocol is paramount. Make it easy for other developers to build on your protocol with developer-friendly APIs. Here are two suggestions to start:

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.

Community engagement also plays an important part. How do developers find you? Where do you connect with developers? What makes your project attractive to build on? Building an active community around your project will help drive adoption in the long term. The crypto developer community is active on various Twitter, Telegram, and Discord channels.

Provide Contract SDKs

Writing and maintaining robust, client libraries for many programming languages is non-trivial. Having SDKs available helps developers build on your protocol.

Contract wrappers built with typechain, truffle-contract, ethers, or web3.js) makes calling contracts as simple as calling Javascript functions. Distribute your SDK as NPM packages that developers can 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 c = await MyContract.deployed();
const result = await c.someFunction(5); // Calls a smart contract

💡 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 comes with a tradeoff: learning how to use it takes time. Good documentation reduces the time developers spend learning.

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.

Tools like leafleth allows you to generate automated documentation using NatSpec comments and produce a website to publish the documentation.

💡To document an HTTP API, check out redoc or slate. You can check out other helpful resources for building HTTP APIs here.

Build CLI Tools and Runbooks

Runbooks are codified procedures to achieve a specific outcome. Runbooks should contain the minimum information necessary to successfully perform the procedure.

Build internal CLI tools and runbooks to improve operations. With smart contracts, this is usually a script containing one or more contract calls that performs a business operation.

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.

💡 To get started, pick an effective manual process, implement it in code, and trigger automated execution where appropriate.

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. If you’re building a DeFi protocol, price slippage alerts are particularly useful to prevent hacks.

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

On Building DApp Backends

DApps need a way to read and transform data from smart contracts. However, on-chain data aren’t always stored in an easy-to-read format. Reading contract data directly from an Ethereum node is sometimes too slow for user-facing web and mobile apps. Instead, you need to index the data into a more accessible format.

theGraph API explorer

theGraph offers a hosted GraphQL indexing service for 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.

Alternatively, you can build your own indexing service. This service would communicate with an Ethereum node and subscribe to relevant contract events, perform transformations, and save the result in a read-optimized format. There are open source implementations you can use as reference if you decide to roll your own. Either way, this service needs to be hosted somewhere.

💡 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 can 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.

Frontend dApps load smart contract data from an Ethereum node through client libraries such as web3.js and ethers.js.

Libraries such as Drizzle, web3-react, and subspace offer higher-level features that simplify connecting to web3 providers and reading contract data.

There are several DApp boilerplates available, such as create-eth-app, scaffold-eth, OpenZeppelin Starter Kit, and Truffle’s Drizzle box. They come with everything you need to start using smart contracts from a React app.

💡 Instead of reading contract data from Ethereum nodes, frontends can also call a backend which indexes smart contract events into a read-optimized format. See the Building DApp Backends section for more detail.

Strive for Usability

Crypto has a usability problem. Gas fees and seed phrases are intimidating for new users. Fortunately, the crypto user experience is improving at a rapid pace.

GSN

Meta Transactions and the Gas Stations Network offers a solution to the gas fee problem. Meta transactions allow services to pay gas fees on behalf of users, removing the need for users to hold Ether. Meta transactions also lets users pay fees in other tokens instead of ETH. These improvements are made possible through clever use of cryptographic signatures. With GSN, these meta transactions are distributed across a network of relayers who pays the gas.

Fortmatic

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

💡 Consider using the web3modal library to add support for major wallets.

Build with Other Protocols in Mind

Ethereum has created a digital finance stack. Financial 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 token lending and borrowing.
  • Uniswap: Digitally-native autonomous token exchange.
  • Augur: Digitally-native prediction market.
  • dYdX: Algorithmically-managed derivative market.
  • UMA: Synthetic token platform.
  • And many more…

Each protocol provides the foundation for other protocols to build more sophisticated products.

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? Compared to building on centralized platforms, access to smart contracts cannot be taken away from under you.

This philosophy extends to centralized services. There’s a growing ecosystem of building blocks available to you:

  • 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.
  • zksync: Protocol for scaling payments and smart contracts on Ethereum.
  • Matic: Faster and extremely low-cost transactions.

There’s an ever growing set of building blocks for you to ship better DApps, faster.

Understand Systemic Risks

Ethereum: The Digital Finance Stack

When you’re building on DeFi, you must assess whether a protocol / currency adds more value than risk.

1. Smart Contract Risk

Smart contracts can have bugs. Always consider the possibility that a bug is found in the protocols you depend on.

The DeFi Score offers a way to quantify smart contract risk. This metric depends 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.

Smart contract risk compounds as more protocols are composed together, similar to how SLA scores are calculated. Because of the permissionless composability of smart contracts, a single flaw cascades into all dependent systems.

2. Counterparty Risk

How is a protocol governed? Some governance models may give direct control over funds or attack vectors to the governance architecture which could expose control and funds.

You can gauge counterparty risk by the number of parties that control the protocol as well as the number of holders.

Different protocols have different degrees of decentralization and control. Be wary of protocols with a small community and limited track record.

3. Mitigating Risk

Mitigate your overall risk exposure by following these basic principles:

  • Interact only with audited smart contracts.
  • Interact only with liquid currencies that has a significant community and product.
  • Purchase smart contract insurance.

Participate in dev communities

Smart contract development is evolving rapidly, with new tools and standards launching from talented teams all over the world.

Ethereum EIPs

Keep up with the latest developments in the space by visiting online Ethereum communities: ETH Research, Ethereum Magicians, r/ethdev, OpenZeppelin Forum, and the EIPs Github repo.

Subscribe to newsletters

Newsletters are a great way to stay up-to-date with the Ethereum ecosystem. I recommend subscribing to Week in Ethereum and EthHub Weekly.

In Closing

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.

If you’re a developer new to crypto, please let me know if you find this guide helpful!

Last updated: 24 June 2020.