May 26, 2026
|
Exploit Postmortem

Denaria Finance's $166K Loss and How Olympix Would Have Prevented It

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:

  1. The attacker took a flash loan from Aave for working capital and deployed helper contracts to act as LPs and traders.
  2. 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 liquidityM into the attacker's position.
  3. The attacker's helper traders executed a sequence of trades. Each trade applied the asymmetric rounding and nudged liquidityM further down. After roughly eight trades, the accumulated drift was enough to push the relevant matrix entry from zero to negative one.
  4. 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 into type(uint256).max.
  5. 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.
  6. The attacker called realizePnL to 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!

What’s a Rich Text element?

The rich text element allows you to create and format headings, paragraphs, blockquotes, images, and video all in one place instead of having to add and format them individually. Just double-click and easily create content.

A rich text element can be used with static or dynamic content. For static content, just drop it into any page and begin editing. For dynamic content, add a rich text field to any collection and then connect a rich text element to that field in the settings panel. Voila!

Headings, paragraphs, blockquotes, figures, images, and figure captions can all be styled after a class is added to the rich text element using the "When inside of" nested selector system.

  1. Follow-up: Conduct a follow-up review to ensure that the remediation steps were effective and that the smart contract is now secure.
  2. Follow-up: Conduct a follow-up review to ensure that the remediation steps were effective and that the smart contract is now secure.

In Brief

  • Remitano suffered a $2.7M loss due to a private key compromise.
  • GAMBL’s recommendation system was exploited.
  • DAppSocial lost $530K due to a logic vulnerability.
  • Rocketswap’s private keys were inadvertently deployed on the server.

Hacks

Hacks Analysis

Huobi  |  Amount Lost: $8M

On September 24th, the Huobi Global exploit on the Ethereum Mainnet resulted in a $8 million loss due to the compromise of private keys. The attacker executed the attack in a single transaction by sending 4,999 ETH to a malicious contract. The attacker then created a second malicious contract and transferred 1,001 ETH to this new contract. Huobi has since confirmed that they have identified the attacker and has extended an offer of a 5% white hat bounty reward if the funds are returned to the exchange.

Exploit Contract: 0x2abc22eb9a09ebbe7b41737ccde147f586efeb6a

More from Olympix:

No items found.

Ready to Shift Security Assurance In-House? Talk to Our Security Experts Today.