Zunami Protocol UZD / zETH SDT-Donation Price-Caching Share-Inflation Drain (2023-08-13)
- Name: Zunami Protocol UZD / zETH SDT-donation price-caching share-inflation drain
- Date: 2023-08-13
- Protocol: Zunami Protocol
- Chain: Ethereum
- Category: donation / share-price manipulation / oracle manipulation / cached-price abuse / strategy-accounting inflation
- Loss: about
1,204 ETHtotal, with public breakdowns of roughly26 WETHfrom the earlier zETH hit and about1,178 ETHfrom the main UZD exploit, or about$2.1M-$2.2Mat the time - Summary: Zunami’s main stablecoin exploit was not a simple AMM spot push by itself. The attacker first bought large amounts of
UZD, then donatedSDTdirectly into Zunami’sMIMCurveStakeDaostrategy and simultaneously pumped the SushiSwap markets that Zunami used to value thatSDT. Because Zunami’stotalHoldings()counted both the strategy’s rawSDTbalance and an attacker-moveablesdtPrice, the strategy suddenly looked much richer than it really was. The attacker then calledcacheAssetPrice()on the UZD contract, freezing that inflated asset price for the rest of the block. Since UZDbalanceOf()and related logic multiplied user share balances byassetPriceCached(), the attacker’s apparent UZD balance jumped from about4.87Mto about16.90Mwithout any legitimate yield having been earned. They then unwound the SDT price manipulation, dumped the inflated UZD into Curve’s UZD pools, repaid the flash loans, and kept roughly1,152 WETHprofit in the main transaction after an earlier smaller zETH drain the same day. - Trigger / entry point:
- Zunami’s elastic stablecoins exposed a public
cacheAssetPrice()path that wrote a block-cached strategy value into_assetPriceCached. - UZD version
1.1extended that cached price into user-facing functions such asbalanceOf,totalSupply, andallowance, so one inflated cache write could affect token accounting broadly for the rest of the block. - One underlying strategy,
MIMCurveStakeDao, treatedtotalHoldings()as_lpBalance * lpPrice + sdt * sdtPrice + config.crv * crvPrice, so directSDTdonations and an attacker-shapedsdtPriceboth increased reported holdings. - The
sdtPricepath depended on thin SushiSwap pairs that a flash-loan attacker could move briefly.
- Zunami’s elastic stablecoins exposed a public
- Root cause:
- Zunami treated strategy-marked holdings as if they were manipulation-resistant net asset value.
- The protocol let one actor increase the strategy’s accounted
SDTinventory by direct donation, then let the same actor mark that donated inventory at an attacker-controlled spot price. - It then cached that manipulated price and reused it in token-balance math rather than only in a delayed or sanity-checked accounting path.
- In practice, Zunami fused two brittle assumptions into one exploit surface: that strategy-side balances only change through trusted yield paths, and that a same-block cached asset price is safe to treat as token balance truth.
- Exploit path:
- The attacker used EOA
0x5f4c21c9bb73c8b4a296cc256c0cde324db146dfand attack contract0xa21a2b59d80dc42d332f778cbb9ea127100e5d75. - In the main UZD transaction
0x0788ba222970c7c68a738b0e08fb197e669e61f9b226ceec4cab9b85abe8cceb, the attacker flash-loaned about7,000,000 USDTfrom Uniswap v3 plus about7,000,000 USDCand10,011 WETHfrom Balancer. - They used those funds to buy into Zunami’s Curve pools, acquiring about
4,082,036 UZDfrom acrvFRAX -> UZDroute and about791,280 UZDfrom acrvUSD -> UZDroute, for about4.87M UZDtotal. - They swapped about
11 WETHfor roughly55,981 SDTand transferred thatSDTdirectly into the vulnerableMIMCurveStakeDaostrategy, increasing the strategy’s rawSDTbalance without minting corresponding UZD shares. - They then performed the price-printing leg on SushiSwap, swapping about
10,000 WETHfor about58,042 SDTin theSDT/WETHpair and about7,000,000 USDTfor about2,154 WETHin theWETH/USDTpair, which together inflated the strategy’s derivedsdtPrice. - With both
sdtbalance andsdtPricedistorted, they calledcacheAssetPrice()on UZD contract0xb40b6608b2743e691c9b54ddbdee7bf03cd79f1c, freezing the manipulated price into_assetPriceCachedfor the rest of the block. - Because UZD balance math used
balance[address] * assetPriceCached() / DEFAULT_DECIMALS_FACTOR, the attacker’s apparent UZD balance jumped from about4,873,316 UZDto about16,902,957 UZD. - The attacker then reversed the SDT and WETH price-push trades, restoring the manipulated markets while keeping the inflated cached UZD accounting alive long enough to matter.
- They sold about
14,158,424 UZDback into one Curve pool for about7,281,124 crvFRAXand about2,744,533 UZDinto another for about1,891,351 crvUSD, then exited into hard assets, repaid the flash loans, and kept about1,152 WETHfrom the main path. - Public postmortems also note an earlier same-day zETH exploit path that drained about
26 WETHbefore the larger UZD transaction.
- The attacker used EOA
- What the attacker needed to be true:
cacheAssetPrice()had to be publicly callable during the manipulated state window.balanceOf()and related token logic had to trust the cached asset price quickly enough that one-block inflation could be monetized.- The
MIMCurveStakeDaostrategy had to count directSDTbalances intotalHoldings()rather than tracking only internally accounted deposits. priceTokenByExchange()forSDThad to rely on thin SushiSwap pairs the attacker could overwhelm with flash-loaned capital.- Curve pools for UZD and zETH needed enough liquidity that the attacker could dump the inflated shares before operators paused the system.
- Technical anatomy worth remembering:
- This was not just
oracle manipulationin the narrow sense of reading a bad AMM price. The decisive failure was that Zunami turned a manipulated strategy valuation into token-balance truth through cached share-price accounting. - It also was not a pure ERC-4626-style donation attack. The attacker did not merely donate assets and borrow against a higher exchange rate elsewhere; they donated
SDTinto a strategy, pumped the strategy’s pricing inputs, cached the resulting NAV, and made their wallet’s UZD balance itself expand. - The same-block cache mattered enormously. Without
cacheAssetPrice(), the attacker would have needed the manipulated strategy value to remain live through all later swaps. By snapshotting it, they could reverse the price-push legs while preserving the inflated UZD accounting long enough to cash out. - Ackee’s postmortem is also worth preserving for process reasons: the vulnerable global caching extension shipped in UZD
v1.1without a fresh audit, and the attacked strategy had been added after an earlier audit of a different protocol version.
- This was not just
- Representative transactions, contracts, and markets:
- Attacker EOA:
0x5f4c21c9bb73c8b4a296cc256c0cde324db146df - Attack contract:
0xa21a2b59d80dc42d332f778cbb9ea127100e5d75 - Main UZD exploit tx:
0x0788ba222970c7c68a738b0e08fb197e669e61f9b226ceec4cab9b85abe8cceb - Earlier zETH exploit tx: publicly cited in REKT as
0x2aec4fdb... - UZD contract with
cacheAssetPrice():0xb40b6608b2743e691c9b54ddbdee7bf03cd79f1c totalHoldings()strategy contract called out by CertiK:0x2ffcc661011bec72e1a9524e12060983e74d14ce- SushiSwap
SDT/WETHpair used in price calculation:0x22def8cf4e481417cb014d9dc64975ba12e3a184 - SushiSwap
WETH/USDTpair used in price calculation:0x06da0fd433c1a5d7a4faa01111c044910a184553
- Attacker EOA:
- Response / mitigation:
- Zunami confirmed the attack shortly after external alerts and stated that base collateral remained secure while the team investigated.
- Public analysis later converged on the cached-price path plus strategy-accounting design as the real exploit surface, not Curve pool logic itself.
- The incident became a useful cautionary example that performance-oriented caching and newly added strategies both belong inside the protocol’s live security boundary and need fresh review when they start affecting solvency or token-balance math.
Why this incident matters
- Zunami broadens the donation / share-price corpus with a mechanically distinct cousin: the attacker did not mainly print fake collateral to borrow elsewhere, but to make their own elastic-balance stablecoin holdings expand inside the token contract and then dump those inflated claims into live liquidity pools.
- More specifically, the exploit path was one joined authority object: strategy-side
totalHoldings()counted attacker-donatedSDTat an attacker-shapedsdtPrice, publiccacheAssetPrice()froze that manipulated NAV into_assetPriceCached, and UZDv1.1then fanned that cached publication out throughbalanceOf(),totalSupply(), andallowance, turning one public cache write into wallet-balance truth for the rest of the block. - It therefore preserves a sharp bridge between
donation / share-price manipulation,oracle manipulation, andstate publication authority failure: strategy-side donation made the accounting lie possible, thin-market AMM manipulation made the donated inventory look expensive, and block-level caching made the transient lie spendable. - The case is a strong reminder that
cached NAVis still an oracle, and if that NAV can be moved by attacker-shaped balance changes or thin AMM marks, caching it can make the exploit easier rather than harder.
Three cache-and-balance-authority promotions this incident collapsed
-
Attacker-shaped strategy value existed inside the MIMCurveStakeDao path
- Donated
SDTsat in the strategy contract and the attacker simultaneously pushed the SushiSwap legs thatpriceTokenByExchange()used to derivesdtPrice. - At this stage the honest statement should still have been narrow:
the strategy currently appears to hold more SDT-valued inventory under a manipulable spot mark. That is not yet the same claim asUZD holders have earned more valueorwallet balances deserve to expand.
- Donated
-
Zunami published that strategy distortion as canonical token-wide asset-price truth
- Public
cacheAssetPrice()promoted the attacker-shaped strategy valuation into_assetPriceCachedat the exact peak of the manipulation window. - This was the decisive publication jump. A thin-market, donation-sensitive strategy estimate stopped being a provisional internal view and became the canonical asset-price object that UZD
v1.1told the rest of the system to trust for the block.
- Public
-
Token semantics and live pool exits spent against that published truth
- UZD
balanceOf(),totalSupply(), and related accounting surfaces reused the cached mark, so the attacker’s wallet appeared to hold far more UZD before any real yield had been earned. - Curve pools then honored that inflated token-side truth when the attacker dumped the expanded UZD balance back into live liquidity for harder assets.
- UZD
The recurring review mistake is to collapse those three promotions into one vague sentence like the asset price was manipulated. Zunami only became a real loss event because attacker-shaped strategy inventory first became canonical cached balance truth and then inherited immediate pool-exit authority.
Pattern tags:
- Strategy-balance donation recognition:
MIMCurveStakeDao.totalHoldings()counted rawSDTsitting in the strategy contract, so direct third-party transfers into the strategy became numerator-side value without minting proportional UZD exposure. - Thin-market multi-hop price printing:
priceTokenByExchange()depended on SushiSwapSDT/WETHplusWETH/USDT, so one flash-loan actor could reshape thesdtPricepath cheaply enough inside a single transaction. - Public cache-write publication failure:
cacheAssetPrice()let any caller promote that attacker-shaped strategy valuation into_assetPriceCachedat the exact peak of the manipulation window. - Token-balance fan-out from cached NAV: UZD
v1.1reused the cached asset price inbalanceOf(),totalSupply(), andallowance, so one bad publication did not stay a misleading view; it became wallet-balance truth across multiple token-facing surfaces. - Design companions: state-publication-authority-failure, sync-harvest-and-cache-write-authority-discipline, internal-mark-publication-quarantine-and-reserve-spend-separation, and delayed-snapshots-and-same-block-quarantine.
Why the exploit worked
- The attacker first acquired a meaningful UZD position, so the later cache write revalued a large existing balance they already controlled rather than only a dust holding.
MIMCurveStakeDao.totalHoldings()collapsed two attacker-shaped inputs into one trusted strategy value: raw donatedSDTbalance and a spot-derivedsdtPricethat could be pushed through thin SushiSwap liquidity.- Zunami exposed the decisive publication step publicly. The same actor who distorted the strategy valuation could immediately call
cacheAssetPrice()before anyone else observed or challenged the mark. - UZD
v1.1then treated the cached mark as token-accounting truth, so the manipulated NAV propagated intobalanceOf()and related user-facing semantics instead of remaining a provisional internal estimate. - Because the cache lasted for the block, the attacker could unwind the visible SDT/WETH price manipulation and still keep the inflated UZD balance live long enough to dump it into Curve liquidity and exit in harder assets.
Detection and audit pivots
- Review every strategy
totalHoldings()/ NAV path that mixes raw token balances with live AMM-derived marks. Ask who can donate into those balances, who can move the pricing venue, and whether both rails can be combined in one transaction. - Treat public
cache,sync,harvest, and valuation-publication helpers as security-sensitive authority surfaces whenever downstream token math, collateral math, or redemption math consumes their outputs. - Separate informational marks from spendable marks. If
balanceOf(),totalSupply(),allowance, redemption math, or collateral admission can jump immediately after one cache write, the protocol has collapsed publication and settlement authority. - Fuzz the exact sequence
acquire shares -> donate into strategy -> move thin pricing venue -> cache mark -> unwind venue -> dump inflated claims, not just isolated oracle-manipulation or donation steps. - Monitor for same-block asset-price or NAV jumps that are not matched by explicit deposits, realized yield, or trusted operator actions, especially when the jump coincides with large venue slippage and immediate redemption / swap activity.
Failure-prevention checklist
- Make strategy NAV depend on accounted positions, not ambient token balances. If
MIMCurveStakeDao.totalHoldings()wants to recognizeSDT, it should do so through explicit strategy-owned accounting or a sweep path that cannot be credited by arbitrary third-party transfers. - Treat thin-governance-token marks as non-canonical for reserve accounting. If a strategy still holds tokens like
SDT, price them through delayed or liquidity-aware oracles, haircut them aggressively, or quarantine them from immediate share-price publication entirely. - Keep cache-write authority away from spendable token semantics. A helper like
cacheAssetPrice()should never let one caller promote a same-block manipulated mark intobalanceOf(),totalSupply(),allowance, redemption, or collateral truth without delay and independent validation. - Separate publication from settlement by at least one state transition the attacker cannot fully script. If a strategy mark changes sharply, wallet-balance semantics and reserve-spending paths should warm up more slowly than the informational view.
- Re-audit whenever a pricing cache fans out into new user-facing surfaces. Ackee’s postmortem is the clean governance lesson here: UZD
v1.1extended caching intobalanceOf()/allowance()and the attackedMIMCurveStakeDaostrategy was added after an earlier audit, so the live solvency object had changed even if the protocol branding had not. - Maintain strategy-level kill switches and per-token quarantine knobs. If one strategy suddenly becomes dominated by donated or thinly priced inventory, operators should be able to exclude it from aggregate NAV or freeze cache publication before pool exits turn the mark into hard-asset losses.
- Alert on
strategy balance jump + thin-market price jump + cache write + immediate large redemptionas one compound incident signature rather than four unrelated monitors.
Audit prompts
- Which exact balances inside
MIMCurveStakeDao.totalHoldings()can be increased by arbitrary third-party transfers, and which of those balances are later multiplied by manipulable spot prices? - What venue composition produced
sdtPrice, how much real depth backed those SushiSwap pairs, and could one flash-loan actor move both legs of the price path in the same transaction? - Can any caller still write a cached mark that immediately changes
balanceOf(),totalSupply(),allowance, redemption ratios, or other spend-authoritative surfaces? - If a wallet first acquires a large UZD position, what exact controls stop that same wallet from donating into a strategy, printing the strategy mark, caching it, unwinding the visible price push, and then dumping the inflated claims before the cache expires?
- What changed after the last audit: new strategies, new pricing venues, new cache fan-out into token semantics, or new helper functions that turned an internal estimate into a public authority surface?
- Does the protocol distinguish
view-layer convenience cachefromcanonical solvency mark, or are those still the same storage slot and the same trust object? - If a strategy-reported NAV jumps sharply without matching deposits or harvested yield, what path prevents the protocol from instantly honoring that jump in user balances or exits?
Lessons that should already be standard
- Caching a manipulable mark does not neutralize it; sometimes it does the opposite. Zunami made the attack practical by preserving a one-block lie long enough for the attacker to unwind the visible price push and still cash out the frozen fiction.
- Strategy inventory is not automatically earned value. If arbitrary users can donate a token into a strategy, raw balance growth is an input-authentication problem before it is a pricing problem.
balanceOf()is solvency-critical when it derives from cached NAV. Once wallet-balance semantics inherit a strategy mark, a bad publication is no longer a misleading dashboard value; it is a spendable claim.- Public publication helpers deserve the same skepticism as admin or oracle updaters when downstream exits trust them.
Anyone can call itis not harmless ifanyonecan call it at the peak of a manipulation window. - Audit scope drift compounds quietly. Zunami’s vulnerable UZD
v1.1cache fan-out and the attackedMIMCurveStakeDaostrategy each changed the live risk object after earlier review, which is exactly how a protocol can keep the same brand while shipping a different solvency boundary. - Donation-sensitive accounting and thin-market pricing become much worse together than apart. The dangerous object was not just
donated SDTor justbad Sushi price; it was their multiplication, publication, and immediate redemptionability inside one transaction.
Internal linkages
- Core recurring pattern: donation-share-price-manipulation.
- Adjacent pattern: oracle-manipulation — the attacker still had to print a manipulable price through thin Sushi markets, but the monetization path ran through cached share-price accounting rather than a simple collateral borrow.
- Publication-authority pattern: state-publication-authority-failure — the transient strategy-side lie only became spendable because
cacheAssetPrice()froze it as canonical token-balance truth for the rest of the block. - Publication-authority companion: sync-harvest-and-cache-write-authority-discipline —
cacheAssetPrice()mattered because one public cache write was allowed to promote attacker-shaped strategy NAV into live token-balance truth immediately. - Publication-quarantine companion: internal-mark-publication-quarantine-and-reserve-spend-separation — the manipulated strategy mark should not have graduated into immediate wallet-balance and redemption authority in the same block it was published.
- Timing companion: delayed-snapshots-and-same-block-quarantine — the exploit specifically depended on freezing a one-block distortion long enough to unwind the visible price-push legs while keeping the inflated UZD balance spendable.
- Presentation / collateral timing companion: reward-recognition-presentation-and-collateral-separation — Zunami collapsed strategy-side recognition, cached presentation, and spendable token-balance semantics into one same-block path instead of graduating them at different speeds.
- Upstream accounting companion: vault-share-truth-security-object — the healthier boundary was one coherent share / asset object where donated strategy inventory, cached NAV publication, and user-visible balance semantics could not drift into separate stories.
- Adjacent pattern: oracle-source-independence-failure — not the main failure here, but a useful reminder that multi-hop onchain price composition can still collapse onto one thin venue family.
Control surface
-
The decisive leverage sat in one publication chain:
MIMCurveStakeDao.totalHoldings()counted raw donatedSDTbalance and spot-derivedsdtPrice;assetPrice()converted that strategy valuation into a token-wide mark; publiccacheAssetPrice()snapshotted it into_assetPriceCached; and UZDv1.1then reused that cached value acrossbalanceOf(),totalSupply(),allowance, and related token accounting as wallet-balance truth. -
Zunami did not merely trust a bad price feed. It let an attacker print strategy NAV, freeze it, and then rely on cache fan-out so the frozen fiction propagated through multiple token-facing consumers as if it were wallet balance.
-
The deeper lesson is that strategy valuation, cache publication, and token-balance semantics were inside the same solvency boundary. Once
balanceOf()inherited the cached mark, the manipulated NAV was no longer just a misleading view — it was the spendable asset the attacker dumped into Curve liquidity. -
Sources:
- https://rekt.news/zunami-protocol-rekt
- https://ackee.xyz/blog/zunami-hack-post-mortem/
- https://www.certik.com/blog/zunami-protocol-incident-analysis
- https://blog.solidityscan.com/zunami-protocol-hack-analysis-e95981976e11/
- https://etherscan.io/tx/0x0788ba222970c7c68a738b0e08fb197e669e61f9b226ceec4cab9b85abe8cceb
- https://explorer.phalcon.xyz/tx/eth/0x0788ba222970c7c68a738b0e08fb197e669e61f9b226ceec4cab9b85abe8cceb
- https://github.com/SunWeb3Sec/DeFiHackLabs
-
Last reviewed: 2026-08-03 UTC