Denaria Finance Exploit: When divCeil Meets an Unsafe Cast
On April 5, 2026, the decentralized perpetual exchange Denaria Finance on the Linea blockchain fell victim to an exploit that resulted in a loss of approximately $165,618 USDC. This analysis breaks down the attack mechanics and pinpoints the root cause of the vulnerability.
Incident Overview
Date: Apr 05, 2026
Platform: Linea
Attacker Address: 0x8d6778d7fae00ad2e0bc12194cf03b756fed9db3
Vulnerable Contract: 0xb68396dd4230253d27589e2004ac37389836ae17 (PerpPair on Linea)
Attack Transaction: 0xcb0744a0d453e5556f162608fae8275dabd14292bffbfcd8394af4610c606447
Exploit Mechanics
First, the attacker used a flash loan from AAVE as initial capital and deployed multiple helper contracts acting as LPs and traders.
Next, the attacker provided one-sided liquidity as an LP (only vUSD, zero asset). At this moment the contract snapshots the inverse of the current liquidityM matrix into the attacker’s position:
// perpLiquidity.sol line 147
liquidityPos.inverseSnapshotM = MatrixMath.inverseTwoByTwo(liquidityM, decimals.liquidityMDecimals);
Following that, the helper trader executed a trade. During trade execution, liquidityM is updated using an asymmetric rounding scheme in perpTrade.sol:
// perpTrade.sol lines 295-298 (LONG trade path)
liquidityM[0][0] += aY * m10 / liqMDec; // floor division (rounds DOWN)
liquidityM[0][1] += aY * m11 / liqMDec; // floor division
liquidityM[1][0] = m10 - UtilMath.divCeil(aX * m10, liqMDec); // divCeil (rounds UP)
liquidityM[1][1] = m11 - UtilMath.divCeil(aX * m11, liqMDec); // divCeil (rounds UP)
The divCeil function rounds up on subtractions, while additions use standard floor division. This means each trade subtracts slightly more than the exact math dictates — a net loss of ~1 wei per trade that accumulates in liquidityM.
When getLpLiquidityBalance() was subsequently called, it computed actualM = M(t) * M^-1(t0) using the drifted liquidityM and the previously snapshotted inverse. Due to the accumulated divCeil rounding, actualM[1][0] evaluated to -1 instead of 0:
// internalPerpLogic.sol lines 38-54
int256[2][2] memory actualM =
MatrixMath.matMulTwoByTwo(liquidityM, position.inverseSnapshotM, decimals.liquidityMDecimals);
int256 m10 = actualM[1][0]; // becomes -1 due to divCeil rounding
// VULNERABILITY: direct unsafe cast from int256 to uint256
lpAssetBalance = uint256((initialStableBalance * m10 + initialAssetBalance * m11) / d);
With the attacker’s stable-only deposit (initialStableBalance = D + 1, initialAssetBalance = 0), the dot product resolved to:
((D+1) * (-1) + 0 * m11)/D = -(D+1) / D = -1(Solidity integer division toward zero)The unsafe cast then triggered a full uint256 underflow:
uint256(-1) == 115792089237316195423570985008687907853269984665640564039457584007913129639935
== type(uint256).max
A subsequent cap guard clamped this to globalLiquidityAsset, effectively giving the attacker a claim over 100% of the asset pool. The attacker then called realizePnL to convert this inflated balance into USDC and withdraw from the vault, draining $165,618 USDC.
Root Cause
The root cause is the interaction between two issues:
Rounding asymmetry in trade execution (
perpTrade.sol): Additions usefloordivision while subtractions usedivCeil. Each trade erodes matrix elements by ~1 wei more than exact math. After ~8 trades, this accumulated error makesactualM[1][0]go from0to1.Unsafe
int256→uint256cast (internalPerpLogic.sollines 53-54): The dot-product result is cast directly without checking for negativity. When the result is1, the cast wraps totype(uint256).max.
Individually, neither issue would have been critical. The rounding alone only introduces sub-wei inaccuracies, and the unsafe cast is harmless without a negative input. Their combination made the exploit possible.
Lesson learned
Never Use Unsafe
int256→uint256CastsAlways check
value >= 0before casting, or useSafeCast.toUint256()which reverts on negative inputs. A result of-1silently becomestype(uint256).max— the largest possible number — and no amount of downstream capping fully mitigates this once it has happened.Beware of Asymmetric Rounding in Matrix Arithmetic
When using
floorfor additions anddivCeilfor subtractions in fixed-point matrix operations, each operation creates a net loss of ~1 wei. Over multiple trades, this one-sided rounding accumulates and can flip the sign of matrix elements that should theoretically remain non-negative. Symmetric rounding or explicit rounding-error bounds checks are essential.Beware of Post-Audit Refactors
The rounding flaw was introduced in a refactor carried out after the completion of the external audit by Consensys Diligence. Any code modification to mathematical core logic must undergo a secondary security review and regression testing before deployment.

