Polter Finance BOO Spot-Oracle Reserve-Ratio Manipulation (2024-11-16)
- Name: Polter Finance BOO spot-oracle reserve-ratio manipulation
- Date: 2024-11-16
- Protocol: Polter Finance
- Chain: Fantom
- Category: oracle manipulation / thin-collateral lending exploit / oracle-adapter history-check failure
- Loss: about 12 million in losses
- Summary: Polter Finance forked Geist’s lending stack onto Fantom and let BOO serve as collateral through an
AaveOracle -> PriceFeedV2 -> ChainlinkUniV2Adapterpath that combined Chainlink data with live SpookySwap reserve ratios. The attacker flash-borrowed almost all BOO from the referenced SpookySwap v2 and v3 pools, leaving only1e6 weiBOO in each pair, then deposited1 BOOinto Polter as collateral. Because the adapter’sgetRoundData(roundId)ignored the requested round and returned the same current spot-reserve price aslatestRoundData(), Polter’s intendedpreviousChainlink0Responsesanity check collapsed: both thecurrentandpreviousprices were attacker-shaped spot reads from nearly empty pools. With the historical guard defeated, Polter valued that single BOO at about$1.373782984830617596e18, and the attacker borrowed out multiple deeper-liquidity markets before unwinding and laundering the proceeds. - Trigger / entry point:
- The exploit centered on Polter’s BOO oracle path, especially
ChainlinkUniV2Adapterat0x875d564a6a86f6154592b88f7a107a517f00cc17andPriceFeedV2at0x80663EDff11e99e8E0B34cb9C3E1fF32E82A80Fe. - Polter’s lending pools accepted BOO as collateral and relied on spot reserve ratios from SpookySwap BOO pairs instead of a manipulation-resistant valuation source.
- The attack transaction was
0x5118df23e81603a64c7676dd6b6e4f76a57e4267e67507d34b0b26dd9ee10eacfrom exploit contract0xA21451aC32372C123191B3a4FC01deB69F91533a.
- The exploit centered on Polter’s BOO oracle path, especially
- Root cause:
- Polter let a thin spot market define BOO collateral value inside a lending protocol that held much deeper, more borrowable assets.
- The intended anti-spike guard in
PriceFeedV2depended on comparing the current BOO price against a prior round, butChainlinkUniV2Adapter.getRoundData()did not return historical data at all; it reused the same live reserve-ratio read aslatestRoundData(). - That meant the protocol’s
previousChainlink0Responsewas not historical truth. It was just the same manipulated spot price fetched a second time. - Once the attacker drained BOO reserves down to dust, the reserve-ratio-derived oracle made
1 BOOappear astronomically valuable and Polter honored that fiction as borrow power.
- Exploit path:
- The attacker borrowed about
269,042 BOOfrom the referenced SpookySwap v3 pair0xed23be0cc3912808ec9863141b96a9748bc4bd89and about1,154,788 BOOfrom the SpookySwap v2 pair0xec7178f4c41f346b2721907f5cf7628e388a7a58, effectively emptying both BOO sides down to1e6 wei. - They deposited
1 BOOinto Polter’s BOO lending market, creating the minimal collateral position needed to monetize the oracle distortion. - During borrow validation, Polter’s
AaveOracleaskedPriceFeedV2for BOO price. That contract in turn read the manipulated reserve ratios from the near-empty BOO pools. - Because
getRoundData(roundId)andlatestRoundData()both returned the same current reserve-ratio-based result, the protocol’spreviousChainlink0Responsecheck failed open: the historical comparison path saw no meaningful difference and did not fall back tolastGoodPrice. - Polter therefore valued
1 BOOat roughly$1,373,782,984,830,617,596, letting the attacker borrow real assets from several lending pools, beginning with about9,134,844 wFTMand extending into other markets including MIM, WSOL, and bridged WETH liquidity. - The attacker drained the victim pools, swapped and consolidated proceeds into FTM, split funds across many addresses, then bridged portions out through SquidRouter and LI.FI before some funds later moved through Tornado Cash on Ethereum.
- The attacker borrowed about
- What the attacker needed to be true:
- BOO had to be accepted as collateral against much stronger assets.
- The referenced SpookySwap BOO markets had to be thin enough that a flash borrower could collapse BOO reserves to dust.
- The oracle path had to trust current reserve ratios rather than a manipulation-resistant price source or tightly bounded fallback.
- The history check had to be fake history:
previousChainlink0Responseneeded to resolve to another current spot read rather than an earlier round. - Borrow caps and collateral constraints on the BOO market had to remain loose enough for one fabricated price to drain multiple sibling pools.
- Technical anatomy worth remembering:
- This was not only a
thin marketfailure. It was a composition failure between a manipulable reserve-ratio oracle and a broken historical-sanity-check implementation. - The subtle bug worth preserving is that the protocol appeared to compare current versus previous data, but both code paths ultimately returned the same live spot-derived answer.
- Polter is a strong oracle-manipulation exemplar because the collateral asset was not Polter’s own token; it was an external ecosystem token whose local DEX depth still sat far below the value Polter let users borrow against it.
- The case is also a reminder that forked code plus inherited audit branding do not cover newly introduced oracle glue. Polter’s own audit page told users the contracts were
identical to Geist, except for the removal of the flash-loan function, yet the BOO pricing stack (AaveOracle -> PriceFeedV2 -> ChainlinkUniV2Adapter) still introduced protocol-specific solvency logic outside that reassurance story.
- This was not only a
Pattern tags
Three thin-collateral price-authority promotions this incident collapsed
-
A local BOO spot-reserve state exists at all
- The referenced SpookySwap v2 and v3 pools still had some BOO balance and Polter’s adapter stack could observe a fresh reserve-ratio-derived price from them.
- This is only the existence question:
there is some market-shaped state object the protocol might later read.
-
That local spot state deserves to count as canonical and historical oracle truth for Polter
- Polter still had to prove that a near-empty BOO pool was deep and independent enough to price lending collateral, and that its
previousChainlink0Responsebranch really consumed earlier state instead of another read of the manipulated present. - This is the oracle-truth question:
this local reserve state really deserves to become current and previous price meaning for BOO, not merelythe adapter returned a number twice.
- Polter still had to prove that a near-empty BOO pool was deep and independent enough to price lending collateral, and that its
-
That accepted oracle truth deserves immediate authority over deeper sibling reserves
- Even after BOO was accepted at an absurd mark, Polter still had to decide how much
wFTM,MIM,WSOL, and bridgedWETHthat one collateral fact could release before any second boundary engaged. - This is the authority-budget question:
this accepted BOO price really deserves this much same-window borrow authority across the rest of the deployment.
- Even after BOO was accepted at an absurd mark, Polter still had to decide how much
- Representative transactions and addresses:
- Main exploit tx:
0x5118df23e81603a64c7676dd6b6e4f76a57e4267e67507d34b0b26dd9ee10eac - Exploiter EOA:
0x511f427Cdf0c4e463655856db382E05D79Ac44a6 - Exploit contract:
0xA21451aC32372C123191B3a4FC01deB69F91533a - SpookySwap v2 BOO pair:
0xec7178f4c41f346b2721907f5cf7628e388a7a58 - SpookySwap v3 BOO pair:
0xed23be0cc3912808ec9863141b96a9748bc4bd89 ChainlinkUniV2Adapter:0x875d564a6a86f6154592b88f7a107a517f00cc17PriceFeedV2:0x80663EDff11e99e8E0B34cb9C3E1fF32E82A80Fe- Victim pools highlighted by CertiK:
pMIM0xa826b29d81caef8c9aa212f172ab3ef00566e91e,pWSOL0x0299553df0fa396c0f6f3456d293608e189c3cf3,pFTM0xbbce4b1513d4285bd7a59c2c63835535151c8e7c,pLZ_WETH0x328c7a684f160c089ebff07ff1b5a417f024979e
- Main exploit tx:
- Response / mitigation:
- Polter paused the platform after the exploit and publicly began tracing funds.
- The team attempted to notify bridges and later negotiated onchain with the exploiter while some funds were already being split and bridged away.
- The incident also drew scrutiny to Polter’s security posture because its docs presented Geist audit reports and stated the contracts were
identical to Geistexcept for flash-loan removal, even though the BOO oracle path carried protocol-specific assumptions that deserved their own review.
Why the exploit worked
- Polter collapsed
a local BOO spot state exists,that spot state still counts as independent oracle truth, andthat accepted truth may underwrite borrowing from deeper sibling reservesinto one unchecked path. Once the attacker drained BOO reserves to dust, the lending engine still treated that local market state as money-good collateral truth. - The fake-history guard was the decisive trust-boundary failure.
previousChainlink0Responselooked like a second, time-separated fact, butgetRoundData(roundId)resolved to another read of the same manipulated present-state reserve ratio. The protocol therefore had apparent redundancy without actual independence. - The borrowed value lived somewhere else. BOO itself was thin and easy to distort, but Polter let that thin market’s printed price reach much deeper pools holding wFTM, MIM, WSOL, and bridged WETH. The exploit paid because local collateral fiction was granted cross-pool reserve authority.
- The fork story weakened review discipline. By presenting the deployment as essentially Geist-without-flash-loans, Polter encouraged reviewers to inherit confidence from upstream core contracts while the real new solvency boundary sat in protocol-specific oracle glue and collateral configuration.
Detection and audit pivots
- Treat
previousoracle branches as code paths to falsify, not comfort labels to trust. Polter’s strongest paper defense was thepreviousChainlink0Responsecomparison, but the real question was whethergetRoundData(roundId)ever returned time-separated state at all. - Review reserve-ratio adapters as solvency code, not price-decoration code. Once BOO’s reserve balance on both referenced SpookySwap pools could be flash-borrowed down to
1e6 wei, the adapter was really letting a dust reserve state set lending-market collateral truth. - Trace where fake diversification collapses. Polter appeared to have both current and historical checks plus two BOO liquidity venues, but the practical security question was narrower: how many genuinely independent, manipulation-resistant facts were left after both
currentandpreviousbranches resolved to live spot reads and both venues referenced the same thin token float? - Ask how much value one absurd collateral mark can immediately reach. The exploit only needed
1 BOOof posted collateral because borrow validation had line-of-sight into several deeper-liquidity sibling pools once the oracle accepted the fabricated mark. - Audit fork claims at the changed edges.
Same as Geist except flash loans removedwas never the right review boundary once Polter added or reconfigured a BOO-specific oracle stack that upstream Geist did not need to underwrite safely.
Failure-prevention checklist
- Require historical guards to consume genuinely time-separated state. If a contract exposes
getRoundData(roundId)or similar chronology APIs, test that changingroundIdchanges the returned observation set and timestamp under adversarial conditions. - Do not let reserve-ratio adapters for thin pools create full borrow power against deeper-liquidity assets. If such feeds exist at all, isolate the market, apply severe haircuts, and cap cross-pool borrow reach to what the collateral can plausibly liquidate at size.
- Treat external ecosystem tokens like BOO as fresh collateral listings, not inherited safe assets, even inside forked lending systems. Upstream audits do not transfer automatically to new oracle adapters, new pool selections, or new collateral-factor decisions.
- Couple fake-history detection to circuit breakers. If
current,previous, and fallback paths collapse onto the same live state read, borrowing should pause or the market should fall back to a much harsher collateral policy immediately. - Bound blast radius per collateral market. One manipulated asset should not be able to drain multiple sibling reserves before responders can freeze the market.
- Keep explicit dependency maps for every oracle branch: which pair, which reserve read, which adapter, which historical store, which fallback, and which operator assumptions each one depends on.
Audit prompts
- Does the supposedly historical branch actually read stored historical data, or does it just recompute the current spot state through a different function name?
- If an attacker flash-borrows almost all of the token from the referenced pools, what exact value does the adapter publish, and how much borrowing power does that create before any fallback triggers?
- Are the protocol’s
current,previous,fallback, andlastGoodPricepaths genuinely independent enough that one manipulated reserve state cannot satisfy all of them at once? - What liquidation depth exists for the collateral at the size the protocol is willing to honor, and how does that compare with the strongest asset pools the borrower can drain against it?
- Which security-critical components differ from the upstream fork the docs cite as audited: collateral listings, adapter composition, pool selection, decimals / reserve math, or guard logic?
- If one collateral mark becomes absurd for one block, what hard caps, delays, or per-market pauses stop that bad price from reaching every borrowable reserve in the same deployment?
Lessons that should already be standard
- Historical checks without historical state are theater. A
previousgetter that just returns the current manipulated answer is not defense in depth; it is duplicated failure. Current spot state existsis not the same claim ashistorical guard passed, and evenhistorical guard passedis not the same claim asdeep sibling pools may now honor this mark immediately.- Thin local spot markets should not be allowed to underwrite deep balance sheets. The right comparison is not
does the feed return a number, butcould the protocol actually liquidate the collateral anywhere near that value. - Fork equivalence claims stop at the first protocol-specific risk adapter. Reusing an upstream lending core does not inherit safety for new collateral, new oracle glue, or new market-configuration assumptions.
- Multi-venue language can hide one shared float. Two BOO pairs were still mostly one manipulable collateral universe once both venues could be emptied in the same flash-loan sequence.
- Blast radius is part of oracle design. If one fabricated collateral mark can immediately reach many deeper-liquidity pools, the oracle problem is already a treasury-containment problem.
Why this incident matters
- Polter broadens the oracle-manipulation corpus with a clean
thin external collateral + broken oracle guardbranch, distinct from protocol-native-token pumps like Moola and from LP-wrapper collateral cases like Cheese Bank. - It preserves a useful lesson about fake defense-in-depth: a fallback or historical-price check is meaningless if the supposedly independent data path resolves to the same manipulable spot state.
- The case is a sharp reminder that oracle adapter code is where many forks stop being the upstream protocol they copied, even when docs still market the deployment as effectively the same audited system.
Internal linkages
- Core recurring pattern: oracle-source-independence-failure.
- More specific subfamily: oracle-source-independence-failure.
- Adjacent pattern: oracle-configuration-failure.
- Positive-design companions: oracle independence inventory and degraded-mode discipline, collateral-admission lag and borrow-side quarantine, and post-authentication bounded-loss design. The healthier-side fix was not
add one more price comparison, but to prove real oracle independence, quarantine thin collateral before it became deep borrow power, and keep one accepted mark from immediately releasing deployment-wide reserves. - Useful contrast: moola-market-moo-oracle-manipulation-2022-10-18 — similar
borrow real reserves against a pumped thin tokenoutcome, but Moola depended on a protocol-native collateral loop whereas Polter depended on an external token and a broken history guard in oracle glue code. - Useful contrast: cheese-bank-cheese-eth-lp-oracle-manipulation-2020-11-06 — Cheese Bank inflated LP-wrapper collateral through reserve-based LP valuation, while Polter inflated the token price input itself through reserve-ratio oracle math.
Control surface
- The decisive leverage sat in BOO collateral admission, oracle venue selection, reserve-ratio math inside
ChainlinkUniV2Adapter, the fake-history guard inPriceFeedV2, and the borrow caps that let one fabricated collateral mark reach multiple pools. - Polter did not fail because BOO was volatile. It failed because a cheap-to-print local spot state was allowed to masquerade as historical, independent, solvency-grade truth.
Sources
- https://rekt.news/polter-finance-rekt
- https://www.certik.com/blog/polter-finance-incident-analysis
- https://blog.verichains.io/p/polter-finance-attack-boo-token-price
- https://polter.gitbook.io/polter/security/audit.md
- https://ftmscan.com/tx/0x5118df23e81603a64c7676dd6b6e4f76a57e4267e67507d34b0b26dd9ee10eac
Last reviewed
- 2026-07-29 UTC