Rodeo Finance ETH-unshETH Camelot TWAP manipulation / ForceInvestment health-factor bypass (2023-07-11)

  • Name: Rodeo Finance ETH-unshETH Camelot TWAP manipulation / ForceInvestment health-factor bypass

  • Date: 2023-07-11

  • Protocol: Rodeo Finance

  • Chain: Arbitrum

  • Category: oracle manipulation / multi-block TWAP poisoning / leveraged-farm integration failure / strategy-sanity-check bypass

  • Loss: about $880k net, with roughly $1.69M-$1.7M routed through the exploit path before about $816k in unshETH was recovered and returned to the lending pool

  • Summary: Rodeo Finance exposed an unreleased ETH-unshETH Camelot CLP strategy whose pricing path relied on a four-sample TWAP updated every 45 minutes from the thin ETH/unshETH pool. The attacker spent several update windows sandwiching that pool so the TWAP drifted far above reality while most of the manipulation capital came back out after each update. Once the oracle was poisoned, the attacker called Investor.earn() against Rodeo’s USDC pool with strategy id 41, forcing the protocol into the unreleased strategy and borrowing about 400,000 USDC against a position whose health check now looked safe only because the manipulated TWAP overvalued the resulting unshETH leg. The exploit then unwound the very pool state Rodeo had just trusted, selling unshETH back into the Camelot pair and leaving the lending pool with bad debt.

  • Trigger / entry point:

    • Rodeo’s farm integration let users open leveraged positions through Investor.earn(address usr, address pol, uint256 str, uint256 amt, uint256 bor, bytes dat).
    • Public replay code and postmortems show the exploiter targeted strategy id 41, described as an unreleased ETH-unshETH Camelot LP / CLP strategy.
    • Rodeo’s postmortem says the relevant TWAP averaged the last four price updates, with one update every 45 minutes.
    • Third-party analyses add that the strategy path was not fully configured, which weakened or bypassed the normal sanity check that should have rejected the manipulated execution.
  • Root cause:

    • Rodeo treated a manipulable reserve-ratio TWAP from the ETH-unshETH Camelot pool as if it were a deep, robust solvency price for leveraged-farm health checks.
    • The attacker did not need to leave all manipulation capital at risk for the full averaging period; by sandwiching the update windows, they could temporarily distort the pool at each refresh while recovering most of the capital after the snapshot.
    • Rodeo then let that poisoned price feed directly into an Investor.earn() path that borrowed from the USDC pool and force-routed capital into the same manipulated market.
    • Multiple public analyses also note a second failure at the integration layer: the selected strategy was not fully configured, so the execution sanity check that should have bounded slippage or strategy validity did not protect the pool.
    • The protocol therefore failed at both layers that mattered: the oracle was too easy to print, and the farm-integration path trusted that printed mark too eagerly.
  • Exploit path:

    1. The attacker funded Arbitrum EOA 0x2f3788F2396127061c46fC07BD0fcb91faAcE328 and spent multiple update windows manipulating the ETH-unshETH Camelot pool around Rodeo’s 45-minute TWAP refreshes.
    2. Public replay notes preserve at least two of those oracle-conditioning transactions, including 0x5f16637460021994d40430dadc020fffdb96937cfaf2b8cb6cbc03c91980ac7c and 0x9a462209e573962f2654cac9bfe1277abe443cf5d1322ffd645925281fe65a2e.
    3. After the TWAP had drifted upward, the attacker called Investor.earn() via attack contract 0xE9544Ee39821F72c4fc87A5588522230e340aa54, targeting the USDC pool 0x0032F5E1520a66C6E572e96A11fBF54aea26f9bE, strategy id 41, and about 400,000 USDC of borrowed funds.
    4. DeFiHackLabs’ replay and contemporaneous reporting show that this path forced Rodeo to swap borrowed USDC through USDC -> WETH -> unshETH, building the manipulated strategy position while Rodeo’s health-factor logic still considered it solvent.
    5. The attacker then sold the prepared unshETH back through the same Camelot market and additional swap routes, monetizing the gap between Rodeo’s stale / inflated solvency mark and the real pool state once the trade itself pushed back the other way.
    6. Public analyses identify main exploit transaction 0xb1be5dee3852c818af742f5dd44def285b497ffc5c2eda0d893af542a09fb25a, while some incident trackers separately highlight 0x98f1e234faac8b7f7ceaffe4e8e0581038678d95710b646db45ec3de47e6c3af as the first malicious transaction in the exploit sequence.
    7. Rodeo later said about 430.845 unshETH worth roughly $816k was recovered and swapped back to USDC, but the lending pool still absorbed about $880k of net damage.
  • What the attacker needed to be true:

    • The ETH-unshETH pool backing the strategy oracle had to be thin enough that one actor could distort it repeatedly at each TWAP refresh.
    • Rodeo had to let a still-unreleased or incompletely configured strategy remain callable through Investor.earn().
    • The health-factor / execution checks had to trust the TWAP enough that a position built from borrowed USDC could pass despite being economically unsound at real executable prices.
    • The protocol had to supply borrowable USDC from a deeper pool than the actual depth supporting ETH-unshETH, so the attacker could print solvency in one venue and withdraw value from another.
  • Technical anatomy worth remembering:

    • This is not the simplest single-block AMM spot read oracle exploit. The preserved lesson is that even a TWAP can be cheaply printable if update timing is predictable and the attacker can condition each refresh separately.
    • The attack also matters because the protocol itself became the forced buyer in the manipulated market. Investor.earn() did not merely honor an inflated collateral mark; it borrowed from the USDC pool and routed the funds into the bad strategy path the attacker had prepared.
    • The unconfigured-strategy detail is important. Public analyses say the strategy address / sanity-check path was not fully configured, which meant oracle failure did not need to do all the work alone.
    • Rodeo’s own recovery note preserves the gross-versus-net distinction that is easy to lose: the exploit path touched about $1.7M, but a large unshETH residue stayed in protocol-controlled positions long enough to recover about $816k.
    • The case sits between plain oracle manipulation and post-trade solvency admission failures: the attacker still needed the fake price, but the farm integration also failed by admitting a borrowed position that only looked healthy under that fake mark.
  • Representative transactions and addresses:

    • Attacker EOA: 0x2f3788F2396127061c46fC07BD0fcb91faAcE328
    • Attack contract: 0xE9544Ee39821F72c4fc87A5588522230e340aa54
    • Rodeo Investor contract used in public replay: 0x8accf43Dd31DfCd4919cc7d65912A475BfA60369
    • Rodeo USDC pool used in public replay: 0x0032F5E1520a66C6E572e96A11fBF54aea26f9bE
    • unshETH: 0x0Ae38f7E10A43B5b2fB064B42a2f4514cbA909ef
    • WETH: 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1
    • USDC: 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8
    • Camelot router used in public replay: 0xc873fEcbd354f5A56E00E710B90EF4201db2448d
    • Manipulation transaction 1: 0x5f16637460021994d40430dadc020fffdb96937cfaf2b8cb6cbc03c91980ac7c
    • Manipulation transaction 2: 0x9a462209e573962f2654cac9bfe1277abe443cf5d1322ffd645925281fe65a2e
    • Main exploit transaction highlighted by PeckShield / DeFiHackLabs: 0xb1be5dee3852c818af742f5dd44def285b497ffc5c2eda0d893af542a09fb25a
    • First malicious transaction highlighted by DN Institute: 0x98f1e234faac8b7f7ceaffe4e8e0581038678d95710b646db45ec3de47e6c3af
  • Response / mitigation:

    • Rodeo paused the protocol, worked with outside researchers, and offered a white-hat bounty for returned funds.
    • The team recovered the residual unshETH, swapped it back into USDC, and said the recovered amount would be redeposited into the lending pool.
    • Rodeo also said the remaining bad debt would be covered over time from treasury resources, while future farm integrations would require stronger oracle assumptions, deeper liquidity, tighter rate limits, and more review before rollout.
    • The broader fix for this family is not just use TWAP: it is prove the averaging window itself is expensive to manipulate, and do not let half-configured strategy paths borrow against oracle-sensitive assets before that proof exists.

Pattern tags

  • oracle-manipulation
  • post-trade-solvency-validation-bypass
  • operator-experimentation-separation-from-live-balance-sheet-authority
  • The decisive exploit primitive was not only Camelot price was wrong; it was Rodeo granted live borrow-side authority to a strategy path whose solvency story depended on that cheap-to-condition price, which is why this note belongs at the oracle / strategy-admission seam rather than as a pure feed-consumption anecdote.
  • Rodeo also preserves the rollout-boundary variant clearly: unreleased or incompletely configured was not just product status metadata because Investor.earn() still let that path borrow real pooled USDC.

Why the exploit worked

  • Rodeo used a thin ETH-unshETH Camelot market as solvency truth even though the attacker only needed to rent distortion at each predictable 45-minute refresh. The averaging rule made the price look slower, not harder to print.
  • The poisoned TWAP was allowed to authorize real borrowing from a deeper USDC pool. That exported shallow-market fiction into a larger balance sheet.
  • The protocol itself became the forced buyer in the manipulated venue. Investor.earn() did not stop at reading the bad mark; it borrowed USDC and routed it into the same strategy path whose inputs the attacker had already shaped.
  • Public analyses indicate strategy 41 was still unreleased or incompletely configured, so the route-sanity / slippage layer that should have blocked or bounded the position did not provide an independent backstop.
  • The exploit therefore succeeded as a composition failure: a cheap-to-condition oracle, a live borrow path, and a weak rollout boundary all promoted the same false solvency story.

Detection and audit pivots

  • Monitor update-window conditioning, not only end-state price deviations. Repeated same-pool distortions immediately around each oracle refresh are the real pre-exploit signature of this family.
  • Alert when a strategy that is flagged unreleased, inactive, experimental, or only partially configured still receives calls through public borrow entrypoints such as Investor.earn().
  • Compare oracle-authorized borrow size against executable unwind depth in the same venue. A large gap between borrowable size and honest exit liquidity is a solvency alert even if the mark itself looks smooth.
  • Diff strategy activation state, route configuration, and slippage-guard configuration together. If one of those is dark or degraded while the others are live, the rollout boundary is already broken.
  • Track health-factor jumps that cluster right after oracle updates or right after strategy-specific configuration changes; those are stronger indicators here than generic price volatility alarms.
  • Ask whether operators can disable one strategy / oracle pair without freezing the whole protocol, because the useful response window is the conditioning campaign before the final monetization trade, not only the last exploit transaction.

Why this incident matters

  • It is a clean oracle-manipulation exemplar for the multi-block TWAP is still cheap if the update windows are gameable branch.
  • It also preserves a useful integration lesson: strategy admission and slippage / sanity checks are part of the oracle boundary when the protocol itself routes borrowed funds into the manipulated venue.
  • It is also a sharp rollout-discipline case: an unreleased or incompletely configured strategy stayed callable through a live borrow path, so testing / staging a strategy and granting it live balance-sheet authority were never cleanly separated.
  • The incident is a good bridge case between pure price-printing attacks and post-trade solvency validation failures, because the protocol both trusted a bad price and under-policed the borrowed strategy execution built on top of that price.

Failure-prevention checklist

  • Treat strategy rollout as balance-sheet rollout. A farm or LP strategy should not be reachable through a public borrow path until oracle assumptions, slippage checks, route sanity, and emergency-disable controls are all production-ready.
  • Prove that any TWAP used for solvency is expensive to condition over its real update cadence, not just expensive to distort for one trade. If updates happen at predictable intervals, model an attacker who only has to rent manipulation capital around each refresh.
  • Keep oracle trust and strategy admission joined. A strategy should only become borrowable if the protocol can also prove that its valuation venue has enough depth, independent references, and bounded same-window slippage for the intended position sizes.
  • Refuse to borrow into strategies whose safety checks are partially configured, bypassed, or running in a degraded mode. Unreleased, experimental, or not fully wired yet is already disqualifying if the path can touch pooled funds.
  • Run post-trade solvency checks against executable unwind assumptions, not just entry-side marks. If the protocol is the forced buyer on the way in, it also needs to survive being the forced seller on the way out.
  • Cap per-strategy exposure, borrow size, and route concentration so one thin venue cannot immediately socialize bad debt across a deeper lending pool.
  • Monitor for TWAP-conditioning behavior around known update windows: repeated same-pool sandwiches, inventory that appears only at refresh boundaries, and positions that become healthy only after a small set of timed prints.
  • Keep fast disable lanes for individual strategies, oracle adapters, and borrow markets so an emerging manipulation campaign can be quarantined before the final monetization transaction lands.

Audit prompts

  • What exact venue, update cadence, and averaging rule turned the ETH-unshETH pool into solvency truth, and how much capital does it really take to condition each refresh window rather than the whole averaging horizon?
  • Can an attacker recover most of the manipulation capital immediately after each oracle update while still leaving the published TWAP far enough from reality to pass health checks?
  • Which path first granted live balance-sheet authority to strategy 41, and what objective control should have kept an unreleased or incompletely configured strategy from being callable through Investor.earn()?
  • Do entry checks and exit checks use the same executable liquidity assumptions, or can a position look solvent on the way in only because the protocol ignores how little depth exists to unwind it honestly?
  • What slippage, route-integrity, and strategy-validity checks run before borrowed funds are routed into the manipulated venue, and can any of them silently fail open when configuration is incomplete?
  • If the oracle venue is thin but the borrow pool is deep, what caps or concentration limits stop the protocol from exporting shallow-market fiction into large-pool bad debt?
  • Which monitors would flag multi-window conditioning before the final exploit: repeated timed sandwiches around refreshes, abnormal health-factor jumps after oracle updates, or the sudden activation of a strategy that was supposed to remain dark?
  • Can operators disable one strategy or one oracle adapter quickly without freezing the whole protocol, and was that lane tested before rollout?

Lessons that should already be standard

  • TWAP is not a synonym for manipulation resistance. If the update cadence is predictable and the pool is thin, the attacker may only need to rent distortion at each refresh boundary.
  • Unreleased but callable is already production risk. A strategy that can borrow real funds is live, no matter what the launch plan said.
  • Oracle review and strategy review are the same security problem when the strategy itself routes pooled funds into the priced venue.
  • Entry-side solvency without executable exit-side liquidity is fiction. If a protocol can only justify a position at a mark it could not survive unwinding, it has not really measured solvency.
  • Thin-venue marks should not be allowed to authorize deep-pool borrowing without independent references, hard caps, or both.
  • A protocol should assume attackers will learn its oracle refresh rhythm faster than operators do. Predictable update windows are part of the attack surface, not an implementation detail.
  • Staging and experimentation need a hard boundary from pooled-fund authority. Still configuring and safe to borrow against cannot share the same public entrypoint.

Internal linkages

Control surface

  • The leverage sat in the ETH-unshETH oracle update cadence, the choice to trust Camelot reserve-ratio TWAP for solvency, the ability to call unreleased strategy 41 through Investor.earn(), and the execution checks that still admitted the position once the price was attacker-shaped.
  • Rodeo did not fail because TWAPs are always weak. It failed because this TWAP was cheap to condition, and the protocol let an incompletely hardened strategy path borrow real USDC against the conditioned mark.

Sources

Last reviewed

  • 2026-07-28 UTC