Skip to main content

Smart contract security guidelines

soliditysmart contractssecurity
Intermediate
Trailofbits
Building secure contracts(opens in a new tab)
September 6, 2020
4 minute read minute read

Follow these high-level recommendations to build more secure smart contracts.

Design guidelines

The design of the contract should be discussed ahead of time, prior to writing any line of code.

Documentation and specifications

Documentation can be written at different levels, and should be updated while implementing the contracts:

  • A plain English description of the system, describing what the contracts do and any assumptions on the codebase.
  • Schema and architectural diagrams, including the contract interactions and the state machine of the system. Slither printers(opens in a new tab) can help to generate these schemas.
  • Thorough code documentation, the Natspec format(opens in a new tab) can be used for Solidity.

On-chain vs off-chain computation

  • Keep as much code as you can off-chain. Keep the on-chain layer small. Pre-process data with code off-chain in such a way that verification on-chain is simple. Do you need an ordered list? Sort the list offchain, then only check its order onchain.

Upgradeability

We discussed the different upgradeability solutions in our blogpost(opens in a new tab). Make a deliberate choice to support upgradeability or not prior to writing any code. The decision will influence how you structure your code. In general, we recommend:

  • Favoring contract migration(opens in a new tab) over upgradeability. Migration systems have many of the same advantages as upgradeable ones, without their drawbacks.
  • Using the data separation pattern over the delegatecallproxy one. If your project has a clear abstraction separation, upgradeability using data separation will necessitate only a few adjustments. The delegatecallproxy requires EVM expertise and is highly error-prone.
  • Document the migration/upgrade procedure before the deployment. If you have to react under stress without any guidelines, you will make mistakes. Write the procedure to follow ahead of time. It should include:
    • The calls that initiate the new contracts
    • Where are stored the keys and how to access them
    • How to check the deployment! Develop and test a post-deployment script.

Implementation guidelines

Strive for simplicity. Always use the simplest solution that fits your purpose. Any member of your team should be able to understand your solution.

Function composition

The architecture of your codebase should make your code easy to review. Avoid architectural choices that decrease the ability to reason about its correctness.

  • Split the logic of your system, either through multiple contracts or by grouping similar functions together (for example, authentication, arithmetic, ...).
  • Write small functions, with a clear purpose. This will facilitate easier review and allow the testing of individual components.

Inheritance

  • Keep the inheritance manageable. Inheritance should be used to divide the logic, however, your project should aim to minimize the depth and width of the inheritance tree.
  • Use Slither’s inheritance printer(opens in a new tab) to check the contracts’ hierarchy. The inheritance printer will help you review the size of the hierarchy.

Events

  • Log all crucial operations. Events will help to debug the contract during the development, and monitor it after deployment.

Avoid known pitfalls

Dependencies

  • Use well-tested libraries. Importing code from well-tested libraries will reduce the likelihood that you write buggy code. If you want to write an ERC20 contract, use OpenZeppelin(opens in a new tab).
  • Use a dependency manager; avoid copy-pasting code. If you rely on an external source, then you must keep it up-to-date with the original source.

Testing and verification

Solidity

  • Favor Solidity 0.5 over 0.4 and 0.6. In our opinion, Solidity 0.5 is more secure and has better built-in practices than 0.4. Solidity 0.6 has proven too unstable for production and needs time to mature.
  • Use a stable release to compile; use the latest release to check for warnings. Check that your code has no reported issues with the latest compiler version. However, Solidity has a fast release cycle and has a history of compiler bugs, so we do not recommend the latest version for deployment (see Slither’s solc version recommendation(opens in a new tab)).
  • Do not use inline assembly. Assembly requires EVM expertise. Do not write EVM code if you have not mastered the yellow paper.

Deployment guidelines

Once the contract has been developed and deployed:

  • Monitor your contracts. Watch the logs, and be ready to react in case of contract or wallet compromise.
  • Add your contact info to blockchain-security-contacts(opens in a new tab). This list helps third-parties contact you if a security flaw is discovered.
  • Secure the wallets of privileged users. Follow our best practices(opens in a new tab) if you store keys in hardware wallets.
  • Have a response to incident plan. Consider that your smart contracts can be compromised. Even if your contracts are free of bugs, an attacker may take control of the contract owner's keys.

Last edit: @pettinarip(opens in a new tab), December 8, 2023

Was this tutorial helpful?

Website last updated: June 19, 2024

Learn

  • Learn Hub
  • What is Ethereum?
  • What is ether (ETH)?
  • Ethereum wallets
  • What is Web3?
  • Smart contracts
  • Gas fees
  • Run a node
  • Ethereum security and scam prevention
  • Quiz Hub
  • Ethereum glossary
(opens in a new tab)(opens in a new tab)(opens in a new tab)
  • About us
  • Ethereum brand assets
  • Code of conduct
  • Jobs
  • Privacy policy
  • Terms of use
  • Cookie policy
  • Press Contact(opens in a new tab)