Worked example · 2023-04
Sentiment Protocol read-only reentrancy via Balancer LP integration — Arbitrum — 2023-04-04
Summary
Sentiment Protocol operated on Arbitrum as a permissionless under-collateralised lending platform. Borrowers could deposit collateral — including Balancer LP tokens — into Sentiment-controlled accounts and borrow against the marked value of that collateral. The marking was performed by an oracle pathway that, for Balancer LP collateral, queried a Balancer pool view function to derive an LP-token price.
The vulnerability sits at the boundary between Sentiment's oracle pathway and Balancer's pool-state mechanics. During a Balancer pool operation that involves an internal joinPool / exitPool callback to a Vault hook, the pool's externally-visible view function returned a price that was inconsistent with the pool's invariant in the post-callback state. The pool's reentrancy guard protected write paths into the same pool but did not protect read paths called from external integrators during the in-flight Vault operation. An attacker constructing a callback that re-entered Sentiment's oracle read pathway from within a Balancer pool operation could therefore observe and act on a stale / manipulated LP-token price.
The exploit shape: the attacker took a flash loan as working capital; opened a Sentiment account with Balancer LP collateral; engineered a Balancer pool operation that, through the pool's internal callback chain, caused Sentiment's oracle to read the LP-token price at a moment when the pool invariant was momentarily inconsistent with the steady-state price; borrowed against the collateral at that favourable mispricing; and closed out, leaving Sentiment with bad debt of approximately $1M.
For OAK's purposes the case is a clean T9.005 (read-only reentrancy sub-class) extraction with a T9.002 (flash-loan) working-capital precondition. The Balancer team had previously published advisories on the read-only reentrancy class in early 2023; protocols that had not retrofitted their integration paths to use Balancer's ensureNotInVaultContext reentrancy-context check were the cohort vulnerable to this pattern. Sentiment is the canonical 2023 case for that cohort.
Timeline (UTC)
| When | Event | OAK ref |
|---|---|---|
| Pre-2023-Q1 | Balancer publishes advisory describing the read-only reentrancy class on Vault-callback-enabled pools; recommends ensureNotInVaultContext for integrators querying pool state from oracle pathways |
T9.005 cohort surface (latent) |
| 2023-Q1 | Sentiment deploys Balancer LP collateral support on Arbitrum; oracle pathway queries pool view function without the recommended reentrancy-context check | T9.005 surface present |
| 2023-04-04 (attack tx window) | Attacker opens flash-loan-funded position; deposits Balancer LP collateral into Sentiment account; constructs Balancer pool operation whose Vault callback re-enters Sentiment's oracle read pathway during the in-flight pool state | T9.005 + T9.002 extraction |
| 2023-04-04 (same tx) | Sentiment oracle returns a stale / manipulated LP-token price; attacker borrows against collateral at favourable mispricing; closes out leaving ~$1M bad debt | T9.005 extraction continues |
| 2023-04-04 (within hours) | Sentiment operations team detects the bad-debt position; pauses affected markets; publishes initial public acknowledgement | (operator response) |
| 2023-04-04 (within hours) | Sentiment publishes a public bounty-style framing offering 5% retention if the attacker returns the remainder; on-chain message channel opened | (recovery channel) |
| 2023-04-05 (~24h post-event) | Attacker returns ~$905K (~95%) to a Sentiment-designated address via on-chain transaction; retains 5% as accepted bounty | near-total recovery |
| 2023-04-05 onward | Sentiment publishes incident write-up; BlockSec, PeckShield, Halborn publish converging post-incident analyses on the read-only reentrancy root cause | (transparency / cohort signal) |
What defenders observed
- The Balancer read-only reentrancy class was already disclosed at advisory level before the Sentiment incident. Balancer had published guidance in early 2023 on the Vault-callback read-only reentrancy pattern and had recommended
ensureNotInVaultContextfor integrators querying pool view functions from oracle paths. Sentiment's affected oracle pathway had not been retrofitted with the recommended check at the time of the exploit. The Sentiment case is therefore a known-vulnerability-not-fixed cohort case in the same operational shape as the Onyx 2024 case at/examples/2024-09-onyx.md— public disclosure, deployable fix, gap in the operator's environment. - Read-only reentrancy is structurally distinct from canonical write-side reentrancy. The 2016 DAO and the 2020 Lendf.Me cases at
/examples/2020-04-lendf-me.mdare write-side reentrancy: the attacker re-enters a state-mutating function on the same protocol mid-transaction to extract twice. Sentiment is read-only reentrancy: the attacker re-enters a view function on an integrated protocol mid-state-transition to obtain a stale / manipulated reading. The integration boundary is what makes the surface emergent — neither protocol on its own is unsafe in isolation, but the composition is. Worked examples that conflate the two sub-classes lose the operationally important distinction. - The recovery channel followed the Euler 2023 pattern but at smaller scale. Sentiment's public bounty-style framing — explicit retention offer, on-chain message channel, designated return address — closely paralleled the recovery-negotiation pattern Euler executed two weeks earlier in March 2023 (see
/examples/2023-03-euler-finance.md). The Sentiment case is structurally smaller and faster (~$1M, ~24h) but operationally similar: the operator-side preparation made the recovery primitive activate. The Penpie 2024 case at/examples/2024-09-penpie.mdshows the same primitive failing to activate when the attacker had no incentive to return; the conditions for activation are not guaranteed. - Flash-loaned working capital scaled the per-iteration mispricing to useful size. Without flash-loaned working capital the per-iteration favorable mispricing would have netted thousands of dollars. T9.002 is the precondition Technique that converts a per-unit oracle inconsistency into a single-transaction ~$1M extraction. The Sentiment, Penpie, Onyx, Beanstalk, and bZx worked examples all share this precondition / extraction structure.
- Detection latency was operator-acceptable; recovery latency was unusually fast for the class. Sentiment paused affected markets within hours of the on-chain attack window. Recovery completed within ~24 hours of the exploit. The base-rate expectation for T9.005 incidents is total loss; recovery here was not the base-rate outcome and should not be assumed in mitigation planning.
What this example tells contributors writing future Technique pages
- T9.005 read-only reentrancy is a first-class sub-class deserving explicit vocabulary. The Sentiment case is the canonical 2023 worked example for the read-only reentrancy via integrated AMM LP-token oracle sub-pattern. Curve / dRome incidents in subsequent months extended the same pattern to Curve LP integrations. Contributors writing the T9.005 page should make read-only reentrancy a labelled sub-class distinct from canonical write-side reentrancy, and should treat the integration-boundary (rather than per-protocol logic) as the load-bearing dimension.
- Cohort monitoring around AMM advisories is a discoverable defender practice. Balancer had published advisory guidance on the read-only reentrancy class before the Sentiment incident; the cohort of integrators querying Balancer pool view functions from oracle paths was discoverable on-chain (DefiLlama, Etherscan integration analysis). The set of those integrators that had retrofitted with the recommended
ensureNotInVaultContextcheck was checkable on-chain. The Sentiment case is the canonical 2023 evidence that this cohort-monitoring discipline was not yet operationalised across the integrators layer at the time of the incident. - T9.002 is the precondition Technique that converts per-iteration mispricing into per-incident millions. Worked examples in the T9.005 sub-classes should preserve the precondition / extraction structure rather than collapsing them into a single classification. The Sentiment, Penpie, Onyx, and Beanstalk worked examples document the same structural pattern across distinct extraction sub-classes.
- The recovery-negotiation primitive is conditional, not universal. The Sentiment case shows the conditions for the primitive can hold at ~$1M in ~24h on Arbitrum; the Euler case shows the conditions can hold at ~$200M in ~2 weeks on Ethereum mainnet; the Penpie case shows the conditions can fail. Mitigation planning should treat the primitive as a conditional rescue surface with operator-side preparation prerequisites (pre-drafted bounty framing, designated return address, no-prosecution authority chain) and attacker-side incentive prerequisites that may or may not hold per incident.
Public references
[sentimentpostmortem2023]— Sentiment Protocol public post-incident statement, including the read-only reentrancy root cause and the recovery framing.[blocksecsentiment2023]— BlockSec post-incident technical write-up of the Sentiment read-only reentrancy exploit.[peckshieldsentiment2023]— PeckShield headline figures and on-chain analytics for the Sentiment drain and recovery.[halbornsentiment2023]— Halborn post-incident technical commentary on the Balancer LP-token oracle reentrancy pathway.[balancerreentrancyadvisory2023]— Balancer public advisory on the read-only reentrancy class and the recommendedensureNotInVaultContextcheck for integrators.
Citations
[sentimentpostmortem2023]— Sentiment's own post-incident statement and bounty-framing public communications.[blocksecsentiment2023]— BlockSec forensic analysis converging on the read-only reentrancy root cause.[peckshieldsentiment2023]— PeckShield headline and analytics coverage.[halbornsentiment2023]— Halborn commentary on the Balancer integration pathway.[balancerreentrancyadvisory2023]— Balancer's pre-incident advisory on the vulnerability class, used here to establish the known-vulnerability-not-fixed framing.
Discussion
Sentiment is OAK's canonical 2023 case for the read-only reentrancy via integrated AMM LP-token oracle sub-pattern, paired with the Curve / dRome cohort cases later in 2023 as the secondary worked examples for the same sub-class. Together they illustrate the integration-boundary nature of the surface: the vulnerability is not within either protocol alone but in the composition of an oracle-querying integrator with an AMM whose Vault callback can re-enter the integrator's read path during an in-flight pool state. Contributors writing the T9.005 technique page should make this composition framing explicit rather than treating each cohort case as an independent incident.
The recovery dimension is structurally informative. Sentiment's ~24-hour recovery via 5% bounty retention was operationally similar to Euler's ~2-week recovery, scaled down. Both incidents activated the recovery-negotiation primitive successfully; both required operator-side preparation (public bounty framing, designated return address, on-chain message channel) and attacker-side incentive alignment. The Penpie 2024 case is the contrasting outcome where the primitive failed to activate. Worked examples should report recovery as a factual outcome, not as a mitigation that retires the underlying T9.005 surface.
The known-vulnerability-not-fixed framing pairs the Sentiment case with Onyx 2024 and Wintermute 2022 as the canonical OAK evidence that publicly-disclosed vulnerability classes recur in operator environments that did not deploy the canonical fix in time. The discoverability of the integrator cohort (on-chain) and of the patch status (on-chain check for the reentrancy-context guard) makes cohort monitoring an actionable defender practice; the Sentiment incident is direct evidence that the practice was not yet operating at sufficient coverage in 2023.