DEV Community 👩‍💻👨‍💻

Michael Bogan
Michael Bogan

Posted on

Secure Smart Contract Tools—An End-to-End Developer’s Guide

No doubt—writing secure smart contracts is hard. Even smart contracts written by senior developers can get hacked. And since these smart contracts often hold a high monetary value, the incentive to hack them is also high. Add in the immutability of web3, and getting security right becomes even more important. As a smart contract developer, smart contract security should be your top priority.

In this article, I will walk through a recently published guide, Security Tooling Guide for Smart Contracts by ConsenSys Diligence. It details 22 security tools from across web3 available at each stage of smart contract development. I’ll highlight several important tools to help make your next smart contract even more secure.

So let’s walk through the guide one development stage at a time.

Preparing for Development

As you begin developing your smart contracts, security should be top-of-mind. My favorite sections of the guide are the tools that can help even as you prepare to code. This includes documentation, linting, and writing reusable code.

First, documentation is key to any development project, and smart contract development is no exception. The Ethereum Natural Specification Format (NatSpec) is a great way to document smart contracts.

NatSpec is a special form of comments added to provide rich documentation for contracts, interfaces, libraries, functions, and events. Consider the following solidity code snippet for a Tree Contract:

  // SPDX-License-Identifier: GPL-3.0
  pragma solidity >=0.8.2 < 0.9.0;

  /// @title A simulator for trees
  /// @author Larry A. Gardner
  /// @notice You can use this contract for only the most basic simulation
  contract Tree {
      /// @notice Calculate tree age in years, rounded up, for live trees
      /// @dev The Alexandr N. Tetearing algorithm could increase precision
     /// @param rings The number of rings from dendrochronological sample
     /// @return Age in years, rounded up for partial years
     function age(uint256 rings) external virtual pure returns (uint256) {
         return rings + 1;
     }
  }
Enter fullscreen mode Exit fullscreen mode

NatSpec commented Solidity Contract

By making use of NatSpec annotations, code can be easily explained to other developers, auditors, or someone just looking to interact with the contract. Simply put, it is clean, readable, and easy to understand.

Next, reusing battle-tested code is another proven way to reduce the risk of vulnerabilities in your smart contracts. There are many widely-used opensource smart contract libraries available, such as OpenZeppelin with pre-written logic for implementing access control, pause functions, upgrades, and more, and Solmate Contracts for optimizing gas usage.

Finally, linting is a valuable tool for finding potential issues in smart contract code. It can find stylistic errors, violations of programming conventions, and unsafe constructs in your code. There are many great linters available, such as ETHLint (Formerly Solium). Linting can help find potential problems—even security problems such as re-entrancy vulnerabilities—before they become costly mistakes.

By considering documentation, linting, and reusable code during smart contract development, you can help ensure a more secure contract. Taking the time to set these up properly will pay off in the long run, both in terms of security and efficiency.

Development

Now let’s look at two categories of tools that can help you while you’re coding—unit tests and property-based testing.

Unit tests are clearly a vital part of creating secure and reliable code. By testing individual units of code, we can ensure that our contracts are functioning as intended and hopefully catch any potential issues before they cause problems in production.

There are several different tools available for writing unit tests for smart contracts. Foundry, Truffle, and Brownie are all popular framework choices that support various programming languages.

Foundry (written in Rust) is a framework for writing smart contracts that includes the testing framework Forge. Forge unit tests can be written directly in Solidity and include many cheatcodes that give you assertions, the ability to alter the state of the EVM, mock data, and more. Foundry also comes with built-in Fuzzing (which we discuss in more detail later in the post).

  // SPDX-License-Identifier: Unlicense
  pragma solidity 0.8.10;

  import "ds-test/test.sol";
  import "../StakeContract.sol";
  import "./mocks/MockERC20.sol";


  contract StakeContractTest is DSTest {
     StakeContract public stakeContract;
     MockERC20 public mockToken;

     function setUp() public {
         stakeContract = new StakeContract();
         mockToken = new MockERC20();
     }

    /// @notice Test token staking with different amount
     function test_staking_tokens() public {
         uint256 amount = 10e18;
         mockToken.approve(address(stakeContract), amount);
         bool stakePassed = stakeContract.stake(amount, address(mockToken));
         assertTrue(stakePassed);
     }
 }
Enter fullscreen mode Exit fullscreen mode

A sample Solidity unit test in Foundry

Truffle is also a framework for building smart contracts. Unit tests in Truffle can be written in Solidity or JavaScript. Often developers use JavaScript-based tests for external interactions with a contract and Solidity tests for assessing a contract’s behavior on the actual blockchain. Truffle uses Mocha for async testing and Chai for assertions.

Brownie is a Python-based framework for developing and testing smart contracts. Brownie integrates with pytest for unit testing and has a stack trace analysis tool for measuring code coverage.

When writing unit tests, aim for high-test coverage by testing as many different parts of the code as possible to ensure that all the functionality works as expected. Foundry does not require an extra plugin to measure test coverage; for other frameworks, there is likely at least one plugin you can add to measure this.

While unit testing is a reliable approach to ensure the correctness of smart contracts, property-based testing allows for the deeper verification of smart contracts. This is a relatively new concept, and it offers a range of advantages over traditional unit testing. Property-based testing focuses on testing the—you guessed it—properties of a smart contract rather than its individual components.

From the guide, “Properties describe the expected behavior of a smart contract and state logical assertions about its execution.” A property must hold true at all times. “Property-based testing tools take a smart contract’s code and a collection of user-defined properties as inputs and check if execution violates them at any point in time.”

Property-based testing is based on the notion of fuzzing, which is a technique for testing a system by introducing random inputs. This means that property-based testing can check a smart contract on a much broader level, as it does not rely on specific inputs provided by the developer. Because of this, it is becoming an increasingly popular method for testing smart contracts.

Using our previous contract as an example, let us use a property-based approach to test a simple staking function. For this we will use Scribble, a specification language and runtime tool that makes this a whole lot easier.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

error TransferFailed();

contract StakeContract {

    mapping(address => uint256) public s_balances;

    /// #if_succeeds {:msg "Stake created successfully"} $result == true;
    function stake(uint256 amount, address token) external returns (bool){
        s_balances[msg.sender] += amount;
        bool success = IERC20(token).transferFrom(msg.sender, address(this), amount);
        if (!success) revert TransferFailed();
        return success;
    }
}
Enter fullscreen mode Exit fullscreen mode

A sample solidity property test in Scribble Notation

Image description
Different tools can use Scribble specifications for property testing

To assess our staking function, we must check that it returns true in the event of successful execution. This can be done by adding a comment to the stake function code, as directed above, without the need for a separate test file. The Scribble CLI tool can then be run to convert the scribble annotations into assertions. Next, these assertions must be run through a fuzzer such as Diligence Fuzzing or Mythril to determine if there are any property violations.

Image description
Reports from an example Diligence Fuzzing campaign

There are several other tools available for property-based testing, such as Foundry. Scribble (used above), Diligence Fuzzing, and Mythril are some of the most recommended. Scribble and Diligence Fuzzing are free and opensource tools built by ConsenSys Diligence. Mythril is a tool designed to detect potential vulnerabilities in Ethereum smart contracts.

I particularly enjoy Scribble because putting together these tests is as simple as adding function annotations.

Post-Development

Finally, let’s look at the last group of tools—post-development. Specifically, monitoring.

Because smart contract code on most blockchains is immutable, you have little to no control over your code once it’s pushed to mainnet. Monitoring can help you be aware of any issues or changes in your code so that you can address them quickly. Not only will this help you improve the security of your contracts, but it will also help you optimize and improve their functionality.

Tools like OpenZepplin’s Defender Sentinels and Tenderly’s Real-Time Alerting are great for monitoring on-chain contracts and wallets.

Tenderly Alerts provides a collection of custom triggers to choose from, allowing you to quickly set up alerts for a variety of activities, such as when a new contract is deployed, when a transaction is sent or received, and when a certain address is targeted.

Defender Sentinel provides real-time security monitoring and alerts through a series of custom parameters that you define—such as if withdrawals cross a specific threshold, if someone performs a critical action like calling transferOwnership, or if a blacklisted address attempts to interact with your contract.

What else?

These are some of the essential parts of smart contract security, but there are numerous other considerations I haven’t covered. For example, secure access control and administration, bug bounties and vulnerability reports, and strategies and contingencies for responding to security incidents. These considerations, and more, are covered in detail in the full guide.

Conclusion

Smart contract security tooling is really important. Hopefully this overview has helped in your quest for understanding the right tools available to you in order to write more secure smart contracts. For more detailed information, check out the complete ConsenSys Diligence guide here.

Top comments (0)

Join us at DEV Want to join the conversation?
 

It's easy! Become a DEV member to follow this post, comment, and more.