Lesson 8 of 21
In Progress

Writing and Testing Solidity Contracts

Now that you have a basic understanding of the Solidity programming language, it’s time to start writing and testing your own smart contracts. In this article, we’ll provide some best practices and tips for writing and testing Solidity contracts to help you avoid common pitfalls and write high-quality code.

Best Practices

Here are a few best practices to follow when writing Solidity contracts:

Use the latest version of Solidity

Make sure you are using the latest version of Solidity to take advantage of the latest features and bug fixes. You can specify the Solidity version you want to use at the top of your contract file using the pragma directive:

pragma solidity ^0.7.0;

The caret (^) symbol means that you want to use the latest version that is compatible with the specified version. In this case, we are using the latest version that is compatible with Solidity 0.7.0.

Use explicit visibility modifiers

Explicit visibility modifiers (public, private, and internal) allow you to control the visibility of your contract’s functions and state variables. Make sure to use explicit visibility modifiers for all your functions and state variables to make it clear to other developers how your contract is intended to be used.

Use require for input validation

Use the require function to validate input before executing any important operations. This will help prevent errors and ensure that your contract behaves as intended.

Here is an example of using require for input validation:

function setCounter(uint256 _newCounter) public {
  require(_newCounter > 0, "Counter must be a positive integer");
  counter = _newCounter;
}

In this function, we use require to check that the _newCounter input is a positive integer. If it is not, the function will throw an exception.

Use assert for internal error checking

Use the assert function to check for internal errors in your contract. This will help you catch errors early and ensure that your contract behaves as intended.

Here is an example of using assert for internal error checking:

function incrementCounter() public {
  uint256 oldCounter = counter;
  counter++;
  assert(counter > oldCounter, "Counter was not incremented");
}

In this function, we use assert to check that the counter variable was actually incremented. If it was not, the function will throw an exception.

Use revert for external error checking

Use the revert function to revert the state of your contract if an external error occurs. This will help you maintain the integrity of your contract’s state and ensure that it behaves as intended.

Here is an example of using revert for external error checking:

function transferFunds(address _to, uint256 _amount) public {
  require(_to != address(0), "Invalid address");
  require(_amount > 0, "Invalid amount");
  if (!_to.send(_amount)) {
    revert("Transfer failed");
  }
}

In this function, we use require to validate the _to and _amount inputs and revert if the send function fails. This will ensure that the contract’s state is not changed if the transfer fails.

Tips

Here are a few tips to keep in mind when writing and testing Solidity contracts:

Use test-driven development

Test-driven development (TDD) is a software development process where you write tests first and then write code to make the tests pass. This helps ensure that your code is correct and complete.

To use TDD with Solidity, you can use the hardhat test command to run your tests. You can also use a testing framework like Mocha to structure your tests and make them easier to write and maintain.

Use a linter

A linter is a tool that analyzes your code and checks for style violations and potential errors. Using a linter can help you catch mistakes early and write cleaner, more readable code.

For Solidity, you can use a linter like Solhint or Solium to lint your code. These linters will check your code for common style violations and potential errors, and provide suggestions for how to fix them.

Use a coverage tool

A coverage tool is a tool that measures the coverage of your tests. It will tell you which lines of code your tests are executing and which ones they are not. This can help you identify areas of your code that are not being tested and make sure you are testing your code thoroughly.

For Solidity, you can use a coverage tool like Solidity-Coverage to measure the coverage of your tests. This tool will provide a report of your test coverage, showing you which lines of code are being covered and which ones are not.

Use a debugger

A debugger is a tool that allows you to step through your code and inspect the state of your variables at different points in the execution. This can be very helpful when you are trying to identify and fix errors in your code.

For Solidity, you can use a debugger like Remix Debugger to debug your code. This debugger will allow you to step through your code and inspect the state of your variables as you go.

Conclusion

In this article, we provided some best practices and tips for writing and testing Solidity contracts. By following these best practices and tips, you can write high-quality, reliable smart contracts that are easy to maintain and test.

Exercises

To review these concepts, we will go through a series of exercises designed to test your understanding and apply what you have learned.

What is test-driven development?

Test-driven development (TDD) is a software development process where you write tests first and then write code to make the tests pass. This helps ensure that your code is correct and complete.

What is a linter?

A linter is a tool that analyzes your code and checks for style violations and potential errors. Using a linter can help you catch mistakes early and write cleaner, more readable code.

What is a coverage tool?

A coverage tool is a tool that measures the coverage of your tests. It will tell you which lines of code your tests are executing and which ones they are not. This can help you identify areas of your code that are not being tested and make sure you are testing your code thoroughly.

What is a debugger?

A debugger is a tool that allows you to step through your code and inspect the state of your variables at different points in the execution. This can be very helpful when you are trying to identify and fix errors in your code.

What is the revert function used for in Solidity?

The revert function is used to revert the state of your contract if an external error occurs. This will help you maintain the integrity of your contract’s state and ensure that it behaves as intended.