Last year, our team at Hashlock uncovered a critical vulnerability in a major restaking protocol's slashing mechanism — one that could have drained a quarter million dollars in staked assets. Here's the methodology behind the find, the class of bug it belongs to, and what every auditor should watch for in similar systems.
Disclaimer: Specific protocol details have been sanitized. This is about the method, not the exploit.
The Engagement
We were brought in to audit the slashing module of a restaking protocol — the part that penalizes operators who misbehave. Slashing logic is deceptively complex: it intersects with delegation accounting, reward distribution, and withdrawal queues. Any miscalculation doesn't just lose precision — it redistributes real money.
The codebase was well-written. Good test coverage. Natspec comments everywhere. The kind of code that makes you think "this will be a clean audit." That's usually when you find the worst bugs.
Phase 1: Understanding the Accounting Model
Before touching a single line for bugs, I spent two full days mapping the accounting model on a whiteboard:
- How shares map to underlying assets
- How slashing events reduce the share-to-asset ratio
- How pending withdrawals interact with post-slash balances
- How delegation accounting propagates slash effects
This is non-negotiable for DeFi audits. If you don't understand the intended math perfectly, you can't spot where it breaks.
I wrote out every state transition as a formula:
assets_per_share = total_assets / total_shares
withdrawal_value = user_shares * assets_per_share
post_slash_ratio = (total_assets - slash_amount) / total_shares
Simple enough. But the devil is in the implementation.
Phase 2: The Rounding Bug Class
Here's what most developers miss about Solidity math: there are no decimals. Every division truncates. And truncation direction matters enormously when you're dealing with adversarial actors.
The golden rule:
Round against the user in deposits (fewer shares), round against the user in withdrawals (fewer assets). Never round in the user's favor.
I've built a mental checklist I run through for every division operation:
- Who benefits from rounding up vs. down?
- Can an attacker trigger many small operations to accumulate rounding errors?
- Is
mulDivused with the correct rounding direction? - Are there sequences of multiply-then-divide that could be reordered?
Phase 3: The Discovery
The vulnerability was in how the protocol calculated the remaining assets after a partial slash. Here's a simplified version of the pattern:
// Intended: reduce each operator's assets proportionally
function applySlash(uint256 totalSlashAmount) internal {
for (uint i = 0; i < operators.length; i++) {
uint256 operatorPortion =
totalSlashAmount * operatorShares[i] / totalShares;
operatorAssets[i] -= operatorPortion;
}
}
See the problem? Each iteration truncates operatorPortion downward. With enough operators, the sum of all truncated portions is less than totalSlashAmount. The difference — the accumulated rounding dust — remains in the pool as unslashed assets.
On a single slash: negligible. But here's where it gets interesting.
The Exploit Path
An adversarial operator could:
- Fragment delegation across many small sub-accounts
- Trigger repeated small slashing events (in protocols where partial slashing is possible)
- Accumulate rounding dust across hundreds of operations
- Withdraw the phantom assets that should have been slashed
Each individual rounding error was 1 wei. But across thousands of fragmented operations, it added up to meaningful value. At the TVL the protocol was heading toward, we calculated worst-case losses of ~$250K before rate limiting would kick in.
The Fix
The correct pattern uses a "remainder" approach:
function applySlash(uint256 totalSlashAmount) internal {
uint256 remaining = totalSlashAmount;
for (uint i = 0; i < operators.length - 1; i++) {
uint256 operatorPortion =
totalSlashAmount * operatorShares[i] / totalShares;
operatorAssets[i] -= operatorPortion;
remaining -= operatorPortion;
}
// Last operator absorbs all remaining (including dust)
operatorAssets[operators.length - 1] -= remaining;
}
Alternatively, protocols can use mulDivUp for slash calculations (round against the operator being slashed) or implement minimum slash thresholds that make dust accumulation uneconomical.
Lessons for Auditors
1. Map the Money Flow First
Before grepping for reentrancy or unchecked, understand where value enters, transforms, and exits. Draw it out. Every DeFi bug is ultimately an accounting bug.
2. Think in Sequences, Not Single Transactions
The scariest bugs aren't in one function call — they're in sequences. Ask: "What happens if this is called 1,000 times with minimal values?"
3. Rounding Direction Is a Security Property
Every a * b / c in your codebase needs a comment explaining which direction it rounds and why that's safe. If it doesn't have one, it's a potential bug.
4. Build Invariant Tests
For this audit, I wrote a Foundry fuzz test with one invariant:
assert(
sumOfAllOperatorAssets + totalSlashed == originalTotalAssets
);
It broke within 50 runs. Invariant testing is the single highest-ROI technique for finding accounting bugs.
5. Calculate Economic Impact
A 1-wei rounding error sounds harmless. But multiply by realistic parameters (number of operators × slash frequency × TVL growth) and present the dollar figure. That's what turns a "Low" into a "Critical."
The Bigger Picture
Rounding bugs are the #1 most underestimated vulnerability class in DeFi. They're not flashy — no flash loans, no reentrancy, no oracle manipulation. Just quiet arithmetic that slowly bleeds value.
As restaking and liquid staking protocols grow in complexity, the attack surface for these bugs grows proportionally. Every additional layer of share-to-asset conversion is another place where rounding can go wrong.
If you're auditing DeFi protocols, build a systematic approach to rounding analysis. It's found us more critical bugs than any other technique.
We're Hashlock, a smart contract security firm specializing in DeFi protocol audits. If your protocol does math with other people's money, we should talk.
Got a rounding bug story of your own? Drop it in the comments — I'd love to compare notes.
Top comments (0)