The mystery behind test coverage

With the increasing complexity of protocols, the importance of thorough testing cannot be overstated, especially when it comes to the complex protocols and state transitions that are commonplace in today's smart contracts.

Today we are talking the criticality of achieving 100% test coverage in smart contracts, the limitations of this metric, and the proactive steps developers can take to ensure the robustness of their protocols.

Why 100% Test Coverage Is Mandatory:

As protocols grow in complexity, the number of potential state transitions
that is, the various states a protocol can find itself
increases exponentially.

The simplest example of a state transition is a staking contract where a state transition occurs when a user, say Alice, deposits tokens, changing the contract's state to reflect Alice's deposit.

Now if we think about contracts outside of staking contracts, such as lending protocols, whatsoever, it is critical to understand that the possible state transitions can go into the thousands and more. Aiming for 100% test coverage is not just beneficial; it's mandatory.

This rigorous testing benchmark is a prerequisite for audits, ensuring that all basic scenarios are accounted for before deeper, more complex analyses begin. If fundamental scenarios contain bugs, auditors may spend undue time addressing these rather than focusing on more sophisticated state transitions and vulnerabilities.

The Insufficiency of 100% Coverage

However, achieving 100% test coverage does not guarantee that the contract is sufficiently tested. This metric can be misleading: a line of code tested in one context is marked as fully covered, ignoring the hundreds of other contexts in which it could be executed. Such a narrow view of test coverage overlooks the nuanced, complex interactions that can occur within smart contracts, leaving potential vulnerabilities unchecked. This is what most developers lack knowledge and the actual reason why an audit is more than a simple “check”. After having covered the most obvious things, I could literally spend months on large codebases to think about more state transitions, this is why an audit is always a time-boxed review.

Developer Strategies for Comprehensive Testing

Developers must adopt a holistic approach to testing, extending beyond basic scenarios to consider intricate interactions within the contract. This involves contemplating various user actions—multiple deposits, sequential interactions like a call to updatePool followed by a deposit, partial withdrawals, and more. The more diverse and comprehensive the testing scenarios, the lower the likelihood of undetected bugs. Creativity during the testing suite is necessary here.

Moreover, it's not about if bugs will be present, but rather how many. This reality underscores the value of multiple audits, particularly for intricate protocols.

Auditors, being human, have inherent limitations in their ability to foresee every possible edge case or state transition. A collective approach to auditing, involving multiple experts and teams, facilitates a broader examination of the protocol, leveraging shared insights to uncover and address potential vulnerabilities more effectively.