May 27, 2026
Denaria Finance Exploit: $166K Lost to Asymmetric Rounding and an Unchecked int256 Cast on Linea
On April 5, 2026, Denaria Finance, a decentralized perpetual exchange on the Linea blockchain, was drained of approximately 165,618 USDC…
Olympix
7 min read
On April 5, 2026, Denaria Finance, a decentralized perpetual exchange on the Linea blockchain, was drained of approximately 165,618 USDC. The root cause was the interaction of two separate defects in the protocol's liquidity accounting. The private _trade function updated its internal liquidity matrix using inconsistent rounding rules: additions rounded down while subtractions rounded up, leaking roughly 1 wei from the matrix on every trade. After enough trades, a matrix entry that should have stayed at zero crossed below zero. A downstream function then cast that negative int256 value to uint256 without checking its sign, turning a value of negative one into the maximum possible unsigned integer. A silent clamp converted that astronomical number into a claim on the entire pool, which the attacker withdrew. The exploited contract was the PerpPair contract at 0xb68396dd4230253d27589e2004ac37389836ae17, and the attack transaction was 0xcb0744a0d453e5556f162608fae8275dabd14292bffbfcd8394af4610c606447.
Denaria paused all user interfaces after the incident, committed to refunding users with open trades or vault balances, and offered the attacker a bounty for return of the remaining funds.
How the Attack Worked
The exploit chained two defects that were each harmless in isolation.
Defect one: asymmetric rounding in _trade. Denaria tracks LP positions through a 2x2 liquidity matrix called liquidityM. On every trade, the private _trade function updates this matrix. The problem is that the update used two different rounding directions. Matrix entries that grow were updated with ordinary integer division, which truncates toward zero and rounds the result down. Matrix entries that shrink were updated with a helper called divCeil, which rounds up. Concretely, in the long-trade path:
liquidityM[0][0] += aY * m10 / liqMDec; // floor, rounds DOWN liquidityM[0][1] += aY * m11 / liqMDec; // floor, rounds DOWN liquidityM[1][0] = m10 - UtilMath.divCeil(aX * m10, liqMDec); // ceil, rounds UP liquidityM[1][1] = m11 - UtilMath.divCeil(aX * m11, liqMDec); // ceil, rounds UP
Rounding a subtraction up means subtracting slightly more than the exact math requires. Rounding an addition down means adding slightly less. Both effects push the matrix in the same direction, so every trade shrank liquidityM by roughly 1 wei relative to its true value. The short-trade path mirrors the same asymmetry, so the drift accumulates regardless of trade direction. The drift is monotonic. It only ever accumulates.
Defect two: an unchecked int256 to uint256 cast. When the protocol needs an LP's current balance, getLpLiquidityBalance computes a signed matrix product using the drifted liquidityM and a snapshot taken when the LP entered. It then casts the signed result directly to an unsigned integer:
lpAssetBalance = uint256((initialStableBalance * m10 + initialAssetBalance * m11) / d);
There is no SafeCast and no check that the value is non-negative. In Solidity, casting a negative signed integer to an unsigned type does not revert. It wraps. A value of negative one becomes type(uint256).max, a 78-digit number.
The exploit path. The attacker assembled the two defects deliberately:
- The attacker took a flash loan from Aave for working capital and deployed helper contracts to act as LPs and traders.
- Acting as an LP, the attacker supplied one-sided liquidity, stablecoin only and zero asset. At deposit time the contract snapshotted the inverse of the current
liquidityMinto the attacker's position. - The attacker's helper traders executed a sequence of trades. Each trade applied the asymmetric rounding and nudged
liquidityMfurther down. After roughly eight trades, the accumulated drift was enough to push the relevant matrix entry from zero to negative one. - The attacker called
getLpLiquidityBalance. With a stablecoin-only position and the now-negative matrix entry, the internal dot product evaluated to negative one. The unchecked cast turned that intotype(uint256).max. - A guard clause that was meant to bound the result did not revert. It silently clamped the astronomical balance down to
globalLiquidityAsset, the entire asset pool. The attacker's position was now credited with 100 percent of the pool. - The attacker called
realizePnLto convert that inflated balance into USDC and withdrew from the vault, taking 165,618 USDC.
The single most important design decision here is the clamp. When an individual LP's computed balance equals or exceeds the entire pool, correct accounting has already been violated, because the sum of all LP balances must equal the pool total. The safe response is to revert. Denaria's code instead silently substituted the whole pool for the malformed value, which converted an accounting bug into a guaranteed full-pool withdrawal. The clamp did not contain the error. It laundered it.
Why the Audit Did Not Catch This
Denaria was not an unaudited protocol. It was audited by Consensys Diligence, one of the most established names in the field. The exploit happened anyway, and understanding why is more useful than treating the audit as a failure.
The rounding flaw was introduced in a refactor carried out after the Consensys Diligence audit completed. The audited code and the deployed code were not the same code. This is not a Denaria-specific lapse. It is the structural reality of how smart contract security is currently practiced. Roughly 90 percent of exploited smart contracts were previously audited. An audit is a point-in-time engagement. It certifies a specific commit at a specific moment. It cannot certify the commits that come after it, and protocols ship code after audits constantly: refactors, optimizations, parameter changes, new features. Every one of those changes is unaudited the day it deploys.
The Denaria refactor is a near-perfect illustration. The change touched mathematical core logic, the most sensitive surface in the entire codebase, and it introduced an asymmetry so subtle that it leaked only 1 wei per trade. No human reviewer reading a diff is reliably going to catch a 1-wei rounding direction mismatch buried in a matrix update. It does not look like a bug. It looks like arithmetic. It only becomes an exploit after it compounds across several trades and meets a second, unrelated weakness in a different file.
This is the gap. Audits are necessary. A protocol should absolutely be audited, and Denaria was. But audits are not sufficient, because the dangerous change is frequently the one made the week after the auditors sign off, and that change ships straight to mainnet with no security gate in front of it.
Worth noting: Denaria's own project documentation explicitly mandated the invariant that this refactor broke. The docs stated that rounding rules for additions and subtractions on the liquidity matrix must be consistent. The team understood the risk well enough to write it down. What they lacked was an automated, continuous mechanism to enforce that the invariant held on every commit. A written invariant that nothing checks is a comment, not a control.
What Olympix Found (Post-Exploit Analysis)
We ran BugPocer, Olympix's internal audit agent, against the Denaria Finance source after the incident. BugPocer identified both defects in the kill chain and produced a passing proof of concept for each.
It is worth being precise about what BugPocer is, because it is easy to mistake it for something it is not. BugPocer is not a static analyzer that pattern-matches against a list of known bug signatures, and it is not a probabilistic AI tool that reads code and offers a plausible-sounding opinion about it. It is an audit agent that reasons about a protocol's specific logic and invariants, then proves its findings by writing executable tests. A finding from BugPocer is not a flag for a human to go verify. It is a flag accompanied by a proof of concept that either passes or fails when you run it. That distinction matters for an exploit like Denaria's, where the vulnerability is not a recognizable anti-pattern but an emergent property of two correct-looking pieces of code interacting across a multi-trade sequence.
The two confirmed High-severity findings:
denaria_style_asymmetric_rounding_liquidityM flagged the upstream accounting drift. It identified that _trade updates liquidityM with floor division on additions and divCeil on subtractions, in both the long and short branches, so the matrix drifts monotonically downward by roughly 1 wei per trade and any entry can eventually go negative. The finding tied the defect directly to the protocol's own documented invariant requiring consistent rounding. The BugPocer-generated PoC deploys two harness contracts, one replicating the live asymmetric rounding and one using consistent floor rounding, then asserts the two matrices stay equal after repeated long and short trades. The test fails against the asymmetric implementation, which is the proof: the drift is real, it is directional, and it compounds.
silent_cap_masks_accounting_invariant_violation flagged the downstream weaponization. It identified that getLpLiquidityBalance casts a signed matrix product to uint256 with no SafeCast and no non-negativity check, then silently clamps any oversized result to the global pool total instead of reverting. The finding traced how the corrupted balance propagates through every consuming path, including _removeLiquidity, calcPnL, realizePnL, and the liquidation and auto-close flows, so the bug is not contained to a single function. The BugPocer-generated PoC sets up a corrupted LP position whose computed balance exceeds the pool, asserts that a correctly defended contract must revert, and demonstrates that the live contract instead returns the entire pool size. The PoC also documents the exact value that flows downstream into the withdrawal gate: the whole pool.
Together the two findings form the complete kill chain. The first describes the rounding drift that drives a matrix entry negative. The second describes the unchecked cast and silent clamp that turn that negative entry into ownership of the pool. Neither finding in isolation tells the whole story, and that is the point. The exploit lived in the interaction, and surfacing it required reasoning about how a defect in perpTrade.sol becomes catastrophic only when it reaches internalPerpLogic.sol.
This analysis was retrospective. BugPocer ran against the deployed source after the funds were already gone. But the deterministic methods underneath it, fuzzing and mutation testing and harness-based invariant checks, are not retrospective by nature. They are built to run continuously, on every commit, inside the CI/CD pipeline. A mutation testing harness checking Denaria's own documented invariant, that additions and subtractions on the liquidity matrix must round consistently, would have failed the moment the post-audit refactor changed a / to a divCeil. Not days later, not after an exploit, but in the pull request that introduced the change, before it ever reached Linea. That is the difference between security as a gate and security as a pipeline. A gate is something code passes through once. A pipeline is something code lives inside.
Takeaway
Neither the rounding asymmetry nor the unchecked cast was independently fatal. The exploit existed in the seam between them, and that seam opened in a refactor that shipped after a Consensys Diligence audit was complete. Smart contract security treated as a one-time gate at audit will keep missing this class of bug, because the dangerous change is the one made on the Tuesday after the auditors sign off, and because the bug itself is an emergent interaction that no single-file review surfaces. Deterministic verification belongs in the pipeline, running against every commit and proving its findings with executable code, not bolted on once and trusted to hold forever.
The dangerous change is rarely the one the auditors saw. It is the refactor that ships the week after. BugPocer runs continuous, PoC-backed analysis against every commit, catching invariant violations like asymmetric rounding and unchecked casts before they reach mainnet. Book a free demo with Olympix to see how it works!
Olympix: Your Partner in Secure Smart Contracts
Olympix provides advanced analysis tools to help developers identify and fix vulnerabilities before they become critical exploits.
Get started today to fortify your smart contracts and proactively shield them from exploits in the evolving Web3 security landscape.
Connect with us on:
Twitter | LinkedIn | Medium | Newsletter