Full Report
One fantastic hacker is better than five good ones. We can make all of the checklists that we want and this will always be the case. Most bugs are not just items from a checklist - they are broken assumptions or mismatches between what the developer intended and what the code actually does. This article is about Spec Thinking - a strategy to uncover assumptions and the implicit rules of the system. A smart contract is an asset management system where actors gain access to benefits through actions. Pretty simple way to break down some code! The author claims that all bugs fall into three categories: missing paths, incorrect happy paths and unexpected paths. Missing paths are just user intentions that aren't there - you can deposit money but can't withdraw. Incorrect Happy Paths are features that exist but there's a fault or mistake in the state transitions. This is "wrong result but the right path". Things like rewards being miscalculated or state not being updated are good examples of this. These are often caught in testing but it becomes harder to find these with larger codebases or very complex flows. Unexpected Paths are parts of a system that do more than intended. Weird edge cases, unsafe flows or badly scoped permissioned are where these bugs exist. In my experience, this is where most bugs are at and threat modeling works the best. Specifications are how the system is supposed to behave. Invariants are statements that always must hold about a system. For instance, total supply must always equal the sum of all balances. Properties define what should hold true after executing a specific state transition. As an example, after withdrawing the user's balance should decrease by the withdrawn amount. These matter because a bad property means something is wrong. If you understand the invariants, intents and expected properties, you can find how to break them. Understanding these parts of the system helps you get better good coverage. The idea is using this understanding of the system to create specifications at three different levels. The first is the high level specification. This is in plain English how it works like "a user cannot lose funds unless they voluntarily withdraw or trade." The second is mid-level specification with state tracking. Finally, the code level specification for function-levels conditions and transitions. Drafting out the properties, invariants and flows of the protocol allow you to understand the code much better. Formal methods teach you to understand the design intentions versus the implementation. You should be asking yourself three questions when working through these specs: What is expected? What is allowed? What was assumed but never enforced? The specification thinking is something I do a good amount. I usually write out the entire flow from a high level step by step to understand what's going on. Over time, I get some properties and invariants for the protocol and see if I can break them. They have a good question that I should probably use more "What does the code assume, but never check?" Overall, a good thought process on bug hunting. However, I felt this post was really dense and touched on a few too many things. If it just touched on the specs, it would have been better imo.
Analysis Summary
# Best Practices: Spec Thinking for Smart Contract Auditing
## Overview
"Spec Thinking" is a strategic auditing framework designed to move beyond checklist-based security. It focuses on uncovering broken assumptions and mismatches between developer intent and code execution. By defining the system as an interaction between **Assets**, **Actors**, and **Actions**, auditors can identify vulnerabilities in the state machine that automated tools or basic checklists often miss.
## Key Recommendations
### Immediate Actions
1. **Define System Invariants:** Identify the "universal truths" of the protocol that must never be broken (e.g., "Total supply must always equal the sum of all balances").
2. **Map All Assets and Actors:** List every address that can interact with the system and every value-bearing token or permission that can be transferred.
3. **Apply the "Three Prompts" to every function:**
* What is expected? (The intended happy path)
* What is allowed? (The actual reachable state transitions)
* What was assumed but never enforced? (The hidden vulnerabilities)
### Short-term Improvements (1-3 months)
1. **Establish Multi-Level Specifications:**
* **High-Level:** Plain English descriptions of user safety (e.g., "Users cannot lose funds unless they trade").
* **Mid-Level:** State-tracking logic (mapping how variables should change).
* **Code-Level:** Precise function-level conditions and return values.
2. **Implement Property-Based Testing:** Transition from unit tests to property tests that verify if specific conditions (e.g., "balance must decrease after withdrawal") hold true across various inputs.
### Long-term Strategy (3+ months)
1. **Formal Verification Integration:** Use tools like Certora or K-Framework to mathematically prove that the code-level implementation matches the design intentions.
2. **Threat Model Maturity:** Shift from "bug hunting" (finding syntax errors) to "vulnerability hunting" (finding structural flaws in the protocol's economic or logic flow).
---
## Implementation Guidance
### For Small Organizations / Solo Developers
- **Focus on Documentation:** Write out the intended "Happy Paths" in plain English before coding.
- **Manual Walkthroughs:** Perform "Spec Thinking" during peer reviews by asking, "What does this code assume about the caller that it doesn't check?"
### For Medium Organizations
- **Formalize Invariants:** Maintain a central document of protocol invariants that must be verified during every CI/CD pipeline run.
- **Fuzzing:** Implement basic invariant-mode fuzzing to stress-test the "Unexpected Paths."
### For Large Enterprises
- **Sink-Source Analysis:** Conduct rigorous mapping of "Sources" (user inputs/levers) to "Sinks" (state changes/asset transfers) to prioritize high-risk code paths.
- **Red Teaming:** Employ dedicated auditors to specifically hunt for "Missing Paths" (liveness issues) and "Incorrect Happy Paths" (logic errors in complex integrations).
---
## Configuration Examples
While "Spec Thinking" is a mental framework, it translates to technical assertions:
**Invariant Example (Solidity/Foundry):**
solidity
// Invariant: System should never be under-collateralized
function invariant_collateralRatio() public {
assert(totalCollateral >= totalDebt);
}
**Property Example (Post-condition):**
solidity
// Property: After a successful withdraw, the balance must reflect the change
function test_withdrawConsistency(uint256 amount) public {
uint256 preBalance = user.balance;
protocol.withdraw(amount);
assert(user.balance == preBalance - amount);
}
---
## Compliance Alignment
- **NIST Cybersecurity Framework:** Aligns with "ID.AM" (Asset Management) and "DE.CM" (Security Continuous Monitoring).
- **ISO/IEC 27001:** Supports "A.12.6.1 Management of technical vulnerabilities."
- **CIS Controls:** Maps to "Control 16: Application Software Security."
---
## Common Pitfalls to Avoid
- **Checklist Reliance:** Thinking that passing a standard security checklist means the contract is "secure."
- **Ignoring the "Missing Path":** Forgetting to check if necessary functionality (like a rescue or withdrawal function) is absent.
- **Confusing Data-Flow with Control-Flow:** Failing to distinguish between an "Incorrect Happy Path" (math error) and an "Unexpected Path" (reentrancy or logic bypass).
- **Implicit Assumptions:** Assuming an external dependency (like an Oracle or Token) will always behave as expected without verifying it in the spec.
---
## Resources
- **Certora (Formal Verification):** [certora.com]
- **Foundry (Fuzzing/Testing):** [book.getfoundry.sh]
- **Dravee’s Tips (Original Author):** [justdravee.github.io]
- **Smart Contract Security Verification Standard (SCSVS):** [github.com/securing/SCSVS]