DEV Community

rim dinov
rim dinov

Posted on

Hunting for Precision: How I Audited Curve’s StableSwap InvariantIn

DeFi, precision isn't just about math—it's about protecting liquidity.
A single rounding error in an AMM's pricing formula can lead to arbitrage opportunities that drain pool reserves or cause significant slippage for users.
While performing a deep dive into the StableSwapNG math, I discovered a subtle yet critical issue: the invariant $D$ calculation was suffering from significant precision loss due to premature integer division.
In this post, I’ll walk you through my methodology for differential fuzzing, how I isolated the rounding error, and how I refactored the math to ensure 100% precision.
The Challenge: The Invariant $D$The core of Curve’s StableSwap is the calculation of the invariant $D$ (the total amount of tokens in the pool if all tokens had the same price).
This is solved using the Newton-Raphson method, an iterative algorithm.Because the EVM doesn't support floating-point numbers, we rely on 256-bit integer arithmetic.
The challenge is balancing accuracy with gas efficiency while ensuring that intermediate calculations don't overflow or—more importantly—truncate bits that are crucial for convergence.
The Discovery: Isolating the Precision DecayThe vulnerability wasn't an "obvious" logic flaw; it was a cumulative precision loss inherent to integer arithmetic.

  1. Establishing the Ground TruthI began by creating a high-fidelity reference model in Python. Using standard integer arithmetic, I replicated the Curve invariant formula to establish a "Ground Truth." This allowed me to map the expected convergence path of the invariant $D$ for any given input set ($xp, A, n$).
  2. Differential Fuzzing ImplementationTo uncover the discrepancy, I developed a test harness using the Ape Framework. The harness acted as a Differential Fuzzer:Harness Setup: It fed identical input vectors into both the deployed Vyper contract and the Python reference model. Trace Analysis: By hooking into the iteration loop, I performed a step-by-step trace of the intermediate values of $D$. This revealed that the Vyper implementation deviated from the reference model within the first 2-3 steps.
  3. Identifying the BottleneckThe logs revealed the culprit—a single line of code where the order of operations was causing massive truncation: # Pre-refactoring: Division occurred within the accumulation, # causing truncation of intermediate bits. D = (Ann * S / A_PRECISION + D_P * _n_coins) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (_n_coins + 1) * D_P) The nested division Ann * S / A_PRECISION was occurring too early, discarding lower-order bits that were significant for the subsequent multiplication with $D$. This truncation was compounded by each iteration, leading to a drift in the final result.
  4. Verification and Root Cause ConfirmationI ran a property-based test suite with randomly generated input vectors ($10^{17}$ to $10^{21}$). The differential fuzzer confirmed that the drift scaled with the magnitude of the liquidity pools, meaning the error was most pronounced in high-TVL environments. The Fix: Refactoring for PrecisionTo restore mathematical precision, I refactored the calculation to separate numerator and denominator components explicitly. By grouping terms before division, we maintain the integrity of intermediate values. # Explicitly grouping terms before division to preserve precision term1: uint256 = unsafe_div(Ann * S, A_PRECISION) term2: uint256 = D_P * _n_coins numerator: uint256 = (term1 + term2) * D

term3: uint256 = unsafe_div((Ann - A_PRECISION) * D, A_PRECISION)
term4: uint256 = unsafe_add(_n_coins, 1) * D_P
denominator: uint256 = term3 + term4

D = numerator / denominator
By ensuring that all multiplication happens before the final division, we utilize the full width of the uint256 type, effectively eliminating the rounding drift.
Final ThoughtsAuditing isn't just about reading code; it's about validating the mathematical assumptions behind it.
Differential fuzzing is a powerful tool in any auditor's arsenal, allowing us to move from "it looks right" to "it is mathematically proven.
"You can find the full audit report and the reproduction code in my research repository:👉 https://github.com/rdin777/curve-math-fuzzing
Have you encountered similar precision issues in your own smart contract audits?
Let's discuss in the comments!

Tags: #defi #security #vyper #blockchain #fuzzing

Top comments (0)