Deus DEI TWAP (2022)
Smart contract security audit by TierZero · 2024-01-15
Independent research. This is an unsolicited security analysis published by TierZero — not a commissioned audit, and no certificate is issued. It reflects our own review of public code and on-chain events.
Findings summary
Independent research — not a commissioned audit.
What the protocol did
Deus Finance issued DEI, a multi-collateral stablecoin deployed across several chains including Fantom. DEI's minting and redemption logic leaned on on-chain price feeds pulled from decentralized exchange pools rather than a curated external oracle network. On Fantom, part of that pricing path ran through a stableswap-style AMM pool (a Curve/Solidly-type "StableV1" pool built for correlated assets like USDC and DEI) that had launched relatively recently and was carrying thin liquidity compared to the value it was being asked to price. The protocol's minting contracts read a time-weighted average price (TWAP) from this pool to decide how much DEI a given amount of collateral was worth.
This is a common shortcut in DeFi: instead of paying for or building a hardened oracle, a protocol treats its own liquidity pool's spot or short-window TWAP as ground truth. It works fine until someone with enough capital decides it doesn't.
The vulnerability
The bug class is TWAP oracle manipulation via insufficient price observation depth, compounded by reflexivity — the same pool (or a pool correlated with it) that priced the collateral was cheap enough to move with a flash-loan-sized trade, and the protocol's own minting activity fed back into the price it was reading.
A TWAP is only as trustworthy as the liquidity and time window behind it. A TWAP computed over a handful of blocks, or over a pool with low reserves relative to the attacker's available capital, is not meaningfully different from a spot price for an attacker willing to hold a manipulated position for a few blocks.
Illustrative pattern of the flaw:
// Naive TWAP consumption — vulnerable pattern
function getCollateralPrice() public view returns (uint256) {
(uint256 cumulativePrice, uint32 blockTimestamp) = pool.currentCumulativePrice();
uint32 elapsed = blockTimestamp - lastObservation.timestamp;
// window is short and liquidity is thin — both attacker-influenceable
return (cumulativePrice - lastObservation.cumulativePrice) / elapsed;
}
function mintDEI(uint256 collateralAmount) external {
uint256 price = getCollateralPrice(); // trusted without bounds
uint256 deiOut = collateralAmount * price / PRECISION;
_mint(msg.sender, deiOut);
}
Nothing here checks pool depth, caps the deviation from a slower-moving reference, or rejects a price that jumped implausibly within the observation window.
How the exploit worked
- The attacker borrowed a large amount of stablecoins via flash loan — no upfront capital required.
- They swapped through the thin StableV1 pool that fed the DEI pricing logic, pushing its reserves (and therefore its short-window TWAP) far from the true market price.
- With the oracle now reporting an inflated collateral value, the attacker supplied collateral (or borrowed against a position priced off that pool) and minted DEI at a rate far more favorable than the pool's real, pre-manipulation price implied.
- They unwound the swap, repaid the flash loan within the same transaction, and kept the DEI minted at the manipulated rate, later selling or redeeming it against real assets.
- Because the whole sequence executed atomically, there was no window for liquidations, keepers, or governance to intervene — by the time the transaction was final, the damage was already priced in.
The attack was repeated in variant form within a short span, which is typical of oracle bugs: the underlying design flaw survives a partial patch if the fix addresses the specific pool but not the pattern of trusting thin, short-window on-chain prices for mint-and-borrow decisions.
How an audit catches this
This is exactly the class of finding we look for on every engagement under our smart contract audit service — oracle trust boundaries are one of the first things we map before touching business logic. Concretely:
- Liquidity-to-mint-cap ratio check. For every price feed used in a minting or borrowing path, we compute the capital required to move it by a material percentage and compare that against the value it can be used to unlock. If a flash loan comfortably covers the move, that's a finding, full stop.
- TWAP window and source review. We verify the observation window is long enough, and the underlying pool deep enough, that intra-transaction or intra-block manipulation can't dominate the average. Single-block or single-transaction TWAPs are treated as spot prices.
- Deviation and sanity bounds. We check whether the protocol caps how far a fresh price reading can move from a slower reference (a longer TWAP, a second independent source, a Chainlink feed) before minting is throttled or paused.
- Composability tracing. Oracle risk rarely lives in one contract — we trace every caller that consumes a price feed and flag any path where an attacker-controlled trade and a mint/borrow call can land in the same transaction. This is the same discipline we apply when reviewing cross-program value flows, a pattern discussed in our piece on composability risk in DeFi integrations.
- Adversarial test cases, not just unit tests. Our code review and audit process includes scripted flash-loan-style simulations against every price-dependent function, not just the ones the team flags as "the oracle."
Remediation
Protocols pricing mintable debt off their own liquidity should move to TTWAPs with multi-block minimums enforced on-chain (not just off-chain convention), pull from multiple independent sources and take a median or bounded aggregate, add hard deviation caps that pause minting rather than silently accept an outlier price, and — where a bespoke pool must be used — size mint caps to the pool's actual depth rather than to demand. None of this is exotic; it is the same checklist we run through in our own security audit checklist for trading-adjacent programs, and it would have flagged this exact dependency before it reached mainnet.
Protocols shipping mint-against-collateral logic on a new or thin pool should treat that pairing as a standing invariant violation until proven otherwise — get it checked before deployment, not after. Talk to us about a smart contract audit.
Need your contracts audited?
Manual review + tooling across TON, Solana and EVM — certificate and full report included.
Get an audit