← back to lessons

Lesson: Rule-38 sub-pattern 38.4 - "fix REPLACED, not reverted"

Banked 2026-06-11 from Aave v3.7 LiquidationLogic cross-reference (HEAD d7919c0).

The pattern

Rule-38 historically hunts: fix partially reverted, vulnerable pattern copy-pasted into a new path, or an invariant the fix relied on weakened by a later commit. This cycle surfaced a FOURTH, distinct shape:

38.4 - the audited fix is REPLACED by a different mechanism that the auditor never reviewed, AFTER the auditor recommended the original mechanism.

Concretely on Aave v3.7: - Pashov [L-01] recommended: clear the collateral bit by reading the borrower's scaledBalanceOf == 0 AFTER the burns/transfers (ground-truth, post-state). - Commit 201aaa6 (Mar 13) implemented exactly that. - Commit 75145ad (Mar 25) then REPLACED the post-transfer ground-truth read with a PRE-transfer PREDICTION of scaled consumption (gas optimization: avoids an extra SLOAD). - The published audit's "Acknowledgement" text still describes the post-transfer approach - so the disclosed report does NOT correspond to the deployed mechanism.

Why this is high-signal

A "prediction" of post-state is a re-derivation. Re-derivations carry assumptions (here: burn-scaled == transfer-scaled rounding, and SUM-vs-original-balance cap == sequential-cap). When those assumptions hold the code is correct; when a later token/index change breaks one, the bit-clearing silently diverges from reality - reintroducing the very finding the audit "closed." Ground-truth post-state reads cannot drift this way.

How to detect it (add to Rule-38 checklist)

  1. For each audit finding, extract the RECOMMENDED fix mechanism (not just "was it fixed").
  2. Find the commit that implemented the recommendation (38.2).
  3. Then check for ANY LATER commit that touches the same lines - especially ones labeled "gas", "refactor", "optimize", "inline". A perf commit that lands AFTER the security fix and rewrites the fixed logic is the 38.4 red flag.
  4. If the later commit swaps ground-truth (post-state read) for prediction (pre-state compute), flag it: write a differential test asserting prediction == reality across fuzz.

Verdict calibration

38.4 divergence is NOT automatically a finding. Aave's replacement was proven equivalent on the depletion axis (burn and transfer both rayDivCeil). The lesson is the DETECTION trigger and the required follow-up (differential Foundry PoC), not an auto-escalate. Drift-from- audited-mechanism = investigate; equivalence-proven = bank as positive case study; divergence- found = escalate.

Cross-target reuse

Applies to any protocol where a security fix uses a post-state ground-truth check and a later perf pass swaps it for a predicted value: lending liquidation bit-clearing, vault share burn-to-zero detection, AMM reserve-fully-drained flags, bridge message-consumed marks. Whenever "did X fully happen?" is answered by re-computing instead of re-reading, 38.4 applies.

Generated 2026-07-02 13:15:05 UTC | auto-sync /15min