Auditing A Solidity Contract: Episode 4 — Testing

AuditOne
Coinmonks
5 min readMay 6, 2024

--

Smart contracts are self-executing codes that form the backbone of the Web3 ecosystem. Smart contracts serve as the foundational threads of the Web3 ecosystem, delicately balancing billions on an open network. Today, we will discuss popular testing tools and techniques used in smart contract development, such as Truffle, Hardhat, Foundry, formal verification, fuzzing, and unit testing. This is a great place to start if you want to learn about Solidity and how to audit smart contracts. This is one article in a series on auditing Solidity smart contracts. The series will cover vulnerabilities and resources that smart contract auditors use.

Testing

Solidity testing entails systematically assessing and validating smart contracts’ performance, security, and functionality.

Creating comprehensive test cases requires identifying potential inputs, outputs, and interactions with the contract under various conditions to cover different scenarios and edge cases. Test cases should contain regular use cases and exceptional scenarios to verify the contract’s behavior in all situations.

Testing Tools

There are three widely adopted testing frameworks within the Ethereum ecosystem: Truffle, Hardhat, and Foundry.

Testing Techniques

1. Formal Verification

Formal verification is a mathematical approach used to confirm the correctness of a system, such as software, hardware, or a smart contract. It involves creating a formal model that precisely defines the system’s expected behavior and then using mathematical techniques to verify whether the actual implementation adheres to this specification.

The process of formal verification involves:

  • Defining Specifications: The desired properties of a contract are defined using formal language — clear and precise statements.
  • Translating into Formal Representation: The contract’s code is transformed into a formal format, often represented using mathematical models or logic.
  • Automated Validation: Automated tools like theorem provers or model checkers are employed to validate the specifications and properties of the contract.
  • Iterative Process: The verification process is repeated to uncover and address any deviations from the intended properties, ensuring the contract is error-free.

Techniques for formal verification

  • Model Checking: Model checking is a formal verification technique used to ensure that a smart contract behaves according to its intended specifications. It involves systematically analyzing a mathematical model of the contract and verifying whether certain properties hold true for that model.
  • Theorem proving: Theorem proving is a method used to establish the correctness of programs, including smart contracts, through mathematical reasoning. This technique involves transforming the description of a contract’s system and its specifications into precise mathematical statements known as logic formulas. The main goal of theorem proving is to demonstrate that these logic formulas are logically equivalent. “Logical equivalence,” also referred to as “logical bi-implication,” is a relationship between two statements where the first statement is true if and only if the second statement is true.
  • Symbolic execution: Symbolic execution is a formal verification technique used to analyze the behavior of smart contracts by operating on symbolic values instead of concrete ones. This method enables reasoning about the properties of a contract’s code in a systematic and exhaustive manner. When a smart contract’s functions are executed using symbolic execution, the input values are represented symbolically rather than using specific, concrete values. For example, instead of providing a fixed value like `x = 5`, you represent it as a symbolic value `x > 5`. This symbolic value represents a range of possible concrete values that satisfy the inequality.

2. Fuzz Testing

Fuzz testing, commonly known as fuzzing, is a dynamic software testing technique designed to uncover application implementation bugs and vulnerabilities by injecting malformed, unexpected, or random data as inputs. This method operates within a black box framework, focusing on the application’s external behavior without requiring insights into its internal code or logic. Fuzzing enhances software security and robustness, offering an automated approach to identifying potential weaknesses that might evade conventional testing methods.

3. Unit Tests

A unit test checks one piece of code, like a function, to ensure it works correctly. These tests are important because they cover all possible scenarios for that specific piece of code and can find bugs that might not show up in other types of tests.

When you perform a unit test, you select certain inputs to see if they give the right output. How good your test is depends on what inputs you choose. Selecting the right inputs is easy for expected situations, but experienced testers are good at selecting unexpected inputs because those are the ones that often reveal bugs in your code.

4. Integration Tests

An integration test tests a combination of units. Even if each part works fine on its own, they might cause unexpected issues when combined. When making integration tests, try to integrate as many parts as possible. However, the more parts you integrate, the harder it is to find out why a test failed. So, a simple strategy is to integrate only parts that affect each other in the final system.

5. Functional Tests

A functional test tests the system, often called “user-story testing,” based on the user stories outlined during the project’s initial requirements phase. These user stories, part of the technical specifications, serve as a guide for writing the code. Functional tests aim to confirm if the system meets these requirements. They’re important because even if unit and integration tests pass, failing a functional test means the system doesn’t fulfill its intended purpose. On the other hand, if all functional tests pass, a few failed unit or integration tests may not be as critical.

In Conclusion

Testing tools and techniques are important in ensuring smart contracts’ reliability, security, and compliance. Smart contracts’ security ultimately contributes to successfully adopting and utilizing blockchain-based applications. This is why smart contract audits, bug bounties, and reviews are crucial in every stage of development. They increase the number of eyes scouting for vulnerabilities and decrease the chance of critical vulnerabilities slipping through.

Stay safe.

Related Articles:

  1. Auditing A Solidity Contract: Episode 1 — Re-entrancy Attack: Learn about the basics of Re-entrancy attacks and the solutions to them.
  2. Auditing A Solidity Contract: Episode 2 — Delegatecall: Deep dive into deligatecall vulnerability and learn how to best address it as a smart contract auditor.
  3. Auditing A Solidity Contract: Episode 3 — Security Analysis: Learn about three common smart contracts security vulnerabilities floating pragma, phishing with tx.origin and block timestamp manipulation.
  4. Auditing A Solidity Contract: Episode 5 — Automated Testing Tools
  5. Auditing A Solidity Contract: Episode 6 — Frontrunning

--

--

AuditOne
Coinmonks

AuditOne offers total protection for digital companies serviced by a decentralised workforce: Security audits, pen-testing, bug bounties and more.