Jimbo’s Protocol shift() Anchor-Bin Price Manipulation Drain (2023-05-28)

  • Name: Jimbo’s Protocol shift() anchor-bin price manipulation drain
  • Date: 2023-05-28
  • Protocol: Jimbo’s Protocol V2
  • Chain: Arbitrum
  • Category: oracle manipulation / internal rebalance-oracle manipulation / protocol-owned-liquidity drain / Liquidity Book active-bin price manipulation
  • Loss: about 4,048 ETH, commonly reported around 7.58M
  • Summary: Jimbo V2 managed its treasury-backed JIMBO market through Trader Joe Liquidity Book bins and a rebalance function, shift(), that treated the current active bin as a trustworthy price anchor. The attacker first added a tiny amount of external liquidity in a maliciously high bin above Jimbo’s normal protocol-owned-liquidity range, then flash-bought enough JIMBO to push the active bin up into that attacker-chosen price region. With the protocol now believing price had legitimately advanced, shift() redeployed about 10% of accumulated WETH into new anchor bins just below the manipulated active bin. The attacker immediately sold JIMBO back into those fresh WETH-rich bins, pushed price back down toward the floor, called reset() to re-arm the structure, and repeated the cycle until roughly 94.5% of the pool’s ETH liquidity was gone.
  • Trigger / entry point:
    • Jimbo V2 used Trader Joe v2 Liquidity Book bins as the live state surface for its treasury-backed token mechanics.
    • shift() was callable once price moved high enough above the prior reference, and it reorganized ETH liquidity around the current active bin.
    • The protocol did not sufficiently constrain whether that active bin reflected deep, protocol-owned market movement or a thin attacker-created external-liquidity spike.
    • By seeding one malicious external bin above Jimbo’s maxBin, the attacker created a fake high-price destination for the active-bin pointer.
  • Root cause:
    • Jimbo treated the live Liquidity Book active bin as if it were an economically trustworthy oracle for treasury rebalancing.
    • shift() then moved real protocol WETH into anchor bins near that attacker-controlled price without a slippage bound, depth check, or requirement that the new active region be backed by meaningful non-attacker liquidity.
    • The protocol’s own rebalance logic therefore converted a briefly printable market state into immediate withdrawable treasury exposure.
    • In effect, Jimbo’s treasury-management algorithm accepted where the active bin currently is as if it were the same thing as where treasury support may safely be deployed.
  • Exploit path:
    1. The attacker funded Arbitrum operations from BNB Chain, then deployed multiple helper contracts to run the exploit sequence.
    2. They bought JIMBO from the live Trader Joe JIMBO/WETH Liquidity Book market and added a small amount of external liquidity in a bin significantly above Jimbo’s normal protocol-controlled range.
    3. Using a 10,000 ETH Aave flash loan, the attacker bought through the protocol’s normal JIMBO bins until the market’s active bin advanced all the way into the malicious high-price bin; Baseline’s postmortem stresses that this consumed all protocol-owned JIMBO liquidity up to maxBin, which itself held about 50% of non-circulating JIMBO supply, plus one extra token to land the active bin inside the attacker-owned destination.
    4. With the active bin now attacker-positioned, the attacker called shift() on JimboController.
    5. shift() removed ETH from lower bins, sent about 90% of the accumulated ETH into the floor, and redeployed the remaining 10% as new anchor bins one to five bins below the manipulated active bin.
    6. Those anchor bins now sat near the fake high price and contained real WETH from Jimbo’s treasury logic.
    7. The attacker sold JIMBO back into the newly created anchor bins at the inflated price, draining that WETH and pushing the active bin back down toward the normal trading range.
    8. They then called reset() so protocol liquidity would be reorganized around the lower active price and made ready for another shift() cycle.
    9. Baseline’s postmortem says the attacker repeated the pump active bin -> shift treasury WETH upward -> dump into anchor bins -> reset loop 11 times across the core exploit sequence, draining about 94.5% of the pool’s ETH liquidity.
    10. After the drain, the attacker bridged the stolen ETH back to Ethereum.
  • What the attacker needed to be true:
    • The attacker had to be able to create or occupy an external Liquidity Book bin above the protocol’s intended price range.
    • A large flash-loan buy had to be enough to move the active bin into that attacker-controlled region.
    • shift() had to trust the live active bin as a safe rebalance reference and redeploy treasury ETH near it.
    • The newly created anchor bins needed to hold enough real WETH that selling back into them was profitable after flash-loan costs.
    • reset() needed to let the attacker re-arm the same structure repeatedly rather than forcing a slower or more heavily constrained unwind.
  • Technical anatomy worth remembering:
    • This was not a vanilla spot-oracle collateral borrow exploit. Jimbo’s own liquidity-management logic was the oracle consumer and the treasury payout rail.
    • The decisive bug was architectural: programmatic liquidity relocation used attacker-printable market state as if it were trustworthy treasury guidance.
    • The exploit depended on where the active bin moved, not just on the headline token price. Once the active bin crossed into the malicious region, Jimbo itself transported WETH there for the attacker.
    • Baseline’s postmortem is useful here because it shows shift() was meant to be callable only after positive price action of roughly 5% above the previous level. The protocol therefore already treated fresh active-bin movement as a meaningful state transition; the bug was letting one flash-loan excursion satisfy that condition and immediately inherit treasury-moving authority.
    • shift() did not directly hand funds to the attacker; it laid out protocol WETH in a structure the attacker could then immediately trade against at a fabricated price.
    • This is a clean CLAMM-native cousin to other internal-rebalance oracle exploits like Indexed Finance: the protocol did not import a bad Chainlink price, it manufactured bad treasury decisions from manipulable internal market state.
  • Representative transactions and contracts:
    • Attacker Arbitrum EOA: 0x102be4bccc2696c35fd5f5bfe54c1dfba416a741
    • Attacker mainnet holding address: 0x5f3591e2921d5c9291f5b224e909ab978a22ba7e
    • Jimbo token contract: 0xc3813645ad2ea0ac9d4d72d77c3755ac3b819e38
    • JimboController contract: 0x271944d9D8CA831F7c0dBCb20C4ee482376d6DE7
    • One attacker exploit contract highlighted in the protocol postmortem: 0xd4002233b59f7edd726fc6f14303980841306973
    • Representative exploit transaction highlighted by rekt.news: 0x3c6e053faecd331883641c1d23c9d9d37d065e4f9c4086e94a3c34bf8702618a
    • Other public exploit transactions from the protocol postmortem: 0xf9baf8cee8973cf9700ae1b1f41c625d7a2abdbcbc222582d24a8f2f790d0b5a, 0xfda5464e97043a2d0093cbed6d0a64f6a86049f5e9608c014396a7390188670e, 0x44a0f5650a038ab522087c02f734b80e6c748afb207995e757ed67ca037a5eda
  • Response / mitigation:
    • Jimbo acknowledged the exploit several hours after community alerts and paused the UI.
    • The team sent onchain and public messages offering the attacker a 10% bounty for return of funds and threatening escalation to law enforcement if no response came.
    • Baseline’s postmortem notes the drain happened only 2 days after the V2 launch, which is a useful reminder that highly stateful treasury mechanics can still be in their most dangerous learning window even after one failed prior version and a rapid relaunch.
    • Public postmortem material framed the future fix direction around changing liquidity structure, taxes, and borrowing design before any relaunch.
  • Key transaction hashes:
    • Representative exploit transaction: 0x3c6e053faecd331883641c1d23c9d9d37d065e4f9c4086e94a3c34bf8702618a
    • Additional exploit transactions from the protocol postmortem: 0xf9baf8cee8973cf9700ae1b1f41c625d7a2abdbcbc222582d24a8f2f790d0b5a, 0xfda5464e97043a2d0093cbed6d0a64f6a86049f5e9608c014396a7390188670e, 0x44a0f5650a038ab522087c02f734b80e6c748afb207995e757ed67ca037a5eda, 0x5cd668b4ba1303f6e524f7e61882fd8858f0e26755627da23dead650f15c71a9, 0xa8bc0301f175d271965ccefeb256e4977cfefe76ef7ee2fe89a70ced2ca6f23d
  • Comparable exploit pattern:

Pattern tags

  • protocol-authored-market-state-oracle-failure
  • oracle-manipulation
  • protocol-native-collateral-fragility
  • Jimbo’s dangerous oracle was not a borrowed Chainlink or TWAP feed. The protocol’s own treasury controller treated Trader Joe Liquidity Book active-bin state as trustworthy rebalance guidance and then spent treasury WETH against it.
  • The sharp bug was not merely flash-loan price manipulation; it was active-bin microstructure became immediate treasury-deployment authority. Once the attacker printed a thin high-price active bin, shift() itself moved real ETH there.
  • The drain only scaled because publication and spend were fused. Jimbo did not quarantine the newly observed high active bin, require depth-backed confirmation, or cap how much treasury WETH one manipulated shift could expose before the attacker sold into it.

Why the exploit worked

  • Baseline’s own postmortem is unusually explicit about the sequence: the attacker first seeded a malicious external bin well above Jimbo’s normal protocol-owned-liquidity range, then used a 10,000 ETH flash loan to buy through the live Jimbo bins until the active bin advanced into that attacker-owned destination. The key point is not only that spot price moved, but that a thin attacker-authored bin became the protocol’s canonical current price region for the next treasury action.
  • Jimbo’s shift() logic then treated that live active bin as if it were safe treasury guidance. Per the postmortem, shift() removed ETH from lower bins, sent 90% to the floor, and redeployed 10% as ETH-only anchor bins one to five bins below the current active bin. In the exploit, that meant Jimbo itself transported real WETH into a fake high-price neighborhood the attacker had just manufactured.
  • REKT’s shorter summary calls this a lack of slippage control in the shift() function, which is directionally right but incomplete. The deeper failure was broader than one missing slippage check: Jimbo collapsed where the active bin currently sits, where treasury support should be redeployed, and where reserve-paying liquidity may safely appear into one same-window trust decision.
  • Because the attacker could immediately dump JIMBO back into those fresh anchor bins, the monetization path required no second oracle mistake and no governance compromise. Jimbo’s own rebalance logic laid out the WETH exit inventory at the manipulated price, and reset() then let the structure be re-armed so the attacker could repeat the same push active bin up -> call shift() -> sell into new anchor bins -> reset() cycle.
  • The missing healthy-side control was therefore not just add a slippage parameter. Jimbo needed a policy boundary between observing local CLAMM microstructure and granting that microstructure treasury authority: depth-aware validation, delayed activation, bounded per-shift deployment, or a stronger multi-bin / time-separated confirmation that the new active region reflected durable non-attacker demand rather than one flash-loan excursion.

Detection and audit pivots

  • Treat CLAMM active-bin, active-tick, or current-range state as adversarial microstructure until proven otherwise. If a treasury controller, floor manager, or rebalance helper reads that state directly, ask what stops one thin external-liquidity print from becoming reserve-deployment authority.
  • Trace same-window paths from push price into new region to protocol redeploys treasury near that region to attacker immediately sells into the new liquidity. If the whole loop can happen in one atomic or near-atomic window, the controller is already spending against a manipulable local mark.
  • Audit rebalance functions as payout rails, not just housekeeping. Jimbo’s shift() did not transfer ETH straight to the attacker, but it created the exact high-price anchor bins that made the drain possible; that means rebalance placement logic lived inside the solvency boundary.
  • Test whether thin or attacker-seeded external liquidity can define canonical local state for protocol-owned-liquidity controllers. A single hostile bin above maxBin should not have been enough to tell Jimbo where treasury support belonged.
  • Demand explicit spend-boundary controls beneath internal market-state publication: per-shift caps, minimum external depth, delayed activation of fresh active regions, or independent checks that the target region remains economically real after the manipulation pressure disappears.

Failure-prevention checklist

  • Keep active bin observed, new price region accepted, and treasury WETH may now be redeployed near it as separate authority steps. Jimbo collapsed those into one same-window trust event.
  • Require depth-aware or survivability checks before a treasury controller follows a new active region. One externally seeded high bin with thin attacker liquidity should never be enough to steer protocol-owned liquidity.
  • Cap how much reserve inventory one rebalance can move toward a freshly observed price region, especially when the move is triggered by a sudden jump rather than by slow organic trading.
  • Add a delay, cooldown, or bounded shadow mode between price moved 5% above the previous level and shift() gaining full authority to reposition treasury inventory near the new active bin.
  • Treat bins outside or just above the protocol’s normal operating range as hostile until proved otherwise. External LP placement above maxBin should not inherit the same trust as protocol-shaped depth inside the intended band.
  • Make reset() part of the threat model. If an attacker can recycle the structure quickly after draining anchor bins, the protocol has accidentally created a repeatable treasury-extraction loop rather than a one-off rebalance mistake.
  • Simulate atomic and near-atomic bundles that do seed thin high bin -> flash-buy into it -> call shift() -> sell into fresh anchor bins -> reset(). If that bundle still pays, the treasury-placement boundary is too weak.
  • Give operators an asset-local degraded mode that can freeze fresh anchor deployment or narrow per-shift ETH movement without disabling unrelated system behavior.

Audit prompts

  • What exactly does the active bin prove here: only where the last marginal trade landed, or something stronger like durable demand worthy of treasury support?
  • Can a thin external LP position above the protocol’s intended range become the canonical active region for treasury placement with no depth or age requirement?
  • If price jumps 5% in one transaction, what explicit mechanism stops shift() from treating that move as full treasury-deployment authority?
  • How much non-attacker liquidity must exist around a new active region before the controller is allowed to lay out reserve inventory there?
  • Can one actor push the active bin upward, trigger shift(), dump into the new anchor bins, and call reset() before any independent confirmation or cooldown runs?
  • Are rebalance helpers reviewed as payout rails with reserve-loss caps, or are they still treated as market-making housekeeping that only needs functional correctness?
  • What prevents bins outside the normal protocol-owned-liquidity range from being used as adversarial steering points for future treasury placement?
  • If a new active region disappears immediately after the buying pressure ends, what exact rule keeps the protocol from having already socialized that fake region into permanent ETH loss?
  • Does the controller distinguish active bin changed from new range deserves protocol-owned support, or are those still the same state transition?

Lessons that should already be standard

  • The pool’s own active-bin pointer is still adversarial state if attackers can cheaply print it with thin external liquidity.
  • Rebalance code is reserve-moving code. A function that only repositions liquidity can still be the protocol’s real payout rail.
  • Current price region and safe place to deploy treasury support are different facts and should usually become true at different times.
  • Anchor liquidity placed near a manipulated active region is economically equivalent to quoting treasury inventory at that fake price.
  • Reset()-style recovery helpers can turn one bad trust decision into a repeatable drain if they let attackers quickly re-arm the same placement logic.
  • Slippage checks alone are not the full answer. The deeper design requirement is to separate local microstructure observation from treasury authority and to bound how much one fresh observation may move reserves.
  • A protocol-controlled liquidity band is not safe just because the protocol created most of it. The dangerous question is what happens when one attacker-owned bin can still steer where the next protocol-owned liquidity appears.

Why this incident matters

Internal linkages

Control surface

  • The leverage sat in Trader Joe Liquidity Book active-bin state, Jimbo’s shift() and reset() rebalance logic, the placement of anchor bins relative to the active bin, and the absence of depth or slippage constraints before treasury WETH was redeployed.
  • Jimbo did not first lose money because an attacker stole admin keys or bypassed a direct withdrawal check. It lost money because its treasury-management algorithm moved real WETH to the exact price zone the attacker had just fabricated.
  • The healthier control object would have separated live active-bin observation from treasury deployment authority, and would have required the new active region to survive stronger depth, timing, or realizability checks before reserve-paying liquidity was repositioned around it.

Sources

Last reviewed

  • 2026-08-04 UTC