A Security Stack

Elliot
4 min readJun 3, 2023

Writing secure smart contracts is a lengthy and elaborate process, encompassing a wide range of testing types, reviews and tools. A security stack is a conceptual model that outlines the combination of tools and strategies employed to ensure the accuracy and absence of bugs in smart contract systems. The structure is akin to the Swiss Cheese Model of security, advocating for the inclusion of as many defense layers as possible to lessen the likelihood of bugs making their way into a live system.

The Security Stack Model

Unit Tests

The base of our security stack is made up of unit tests, the most cost-effective method for identifying bugs. They screen for rudimentary issues like the enforcement of Access Control Lists (ACLs), validation of user inputs, the fundamental correctness of algorithms, and prevention of state desynchronization due to reentrancy in contracts.

Fuzz Tests

Next in our stack are fuzz tests. As black box tests, they generate random inputs to catch complex bugs at the system’s edges without understanding the program. The effectiveness of these tests relies not only on the duration they’re run but also on the diversity and quality of the test cases generated.

Integration Tests

Integration tests follow next, focusing on the system in its entirety. After the program is parameterized, deployed on mainnet, and integrated, these tests confirm the correctness of the deployment scripts, examine the operation of our program on a forked version of mainnet, and ensure that any live system updates work as expected. They should also test successive function calls to ensure the system operates correctly across multiple states.

Static Analysis

With a solid foundation provided by the lower layers, we can apply static analysis. Although it may flag numerous false positives, each issue warrants careful evaluation for possible security vulnerabilities. Tools such as Mythril, Slither, or Pyrometer can flag a broad range of issues.

As we move on and up to the more expensive layers of the stack, we get closer to our ultimate goal — a secure system.

Symbolic Tests

Symbolic tests form the next layer. They can catch elusive bugs only apparent when a specific program path is followed and verify that certain invariants remain unbroken in a variety of system states. Tools like Echidna, which builds up smart contract state, are excellent for maintaining invariants. Symbolic tests may catch bugs that have slipped past the previous stack layers.

Formal Proofs

Formal tools model our smart contract system as a mathematical equation, with a solving engine validating whether the system behaves exactly as we anticipated. These user-defined tests are as effective as their inputs. Ignoring an invariant during the verification of a complex system could allow a bug to make its way into production. Although formal proofs can be more time-consuming to write than standard unit or integration tests, their ability to identify issues complements the strengths of other testing methods, such as fuzz testing.

Code Reviews

Code reviews with both internal and external developers should occur throughout the development lifecycle. This process ensures architectural correctness and provides developers with an opportunity to articulate their thoughts as the project evolves, leading to a polished end product. Engaging external developers early in the Software Development Life Cycle (SDLC) is beneficial for reviewing changes and assisting with architecture design. Find the checklist for internal and external reviews here, and be sure to check out this article on audit logs.

Audits

Towards the end of the development lifecycle, audits become an essential, albeit costly, part of the stack. They provide valuable insights from expert security engineers. For an efficient and prompt audit, consider audit contest services like Code4rena and Sherlock, that have skilled security researchers who can audit code on short notice.

Ideally, no critical vulnerabilities are found during an audit. However, if any are discovered, the codebase will necessitate further reviews by security engineers during fixes, and potentially require another audit.

Bug Bounties

The final and most expensive layer of our security stack is bug bounties. Major protocols, such as layerzero, offer large rewards for discovering critical vulnerabilities in their production systems. If bugs are found in production leading to bounty payouts, it indicates that earlier stack layers have failed. This necessitates further investment in earlier layers to catch bugs before they reach production.

Conclusion

No single item in the security stack is a silver bullet. Ensuring smart contract system security is an ongoing process requiring constant focus and innovation. Prioritizing resources at the stack’s base results in fewer bugs appearing at the top. Conversely, finding many bugs at the top suggests the need to slow down development and invest more heavily in the lower stack layers.

Special thanks to Kurt Barry, as well as Mooly and Uri from Certora for their feedback.

--

--