Worked example · 2024-09
Penpie Finance reward-claim exploit — Ethereum + Arbitrum — 2024-09-03
The Ethereum drain (~$26.7M) executed in three transactions between 18:23 and 18:42 UTC; a follow-on ~$621K gUSDC drain on Arbitrum executed at 19:14 UTC, after Pendle had paused on Ethereum at 18:45 UTC but before the Arbitrum deployment was paused.
Penpie offered a 10% white-hat bounty (~$2.7M) on 2024-09-06; the attacker did not accept. Over the following days the attacker laundered the proceeds through Tornado Cash — ~5,600 ETH in the first ~12 hours, the remainder in subsequent batches over the following week.
- OAK-T9.005 (Reentrancy — cross-function / cross-protocol variant) as the extraction mechanism.
- OAK-T9.004 (Access-Control Misconfiguration — permissionless
registerPenpiePoolwithout market-validity gating) as the precondition that admitted the attacker-controlled market into Penpie's reward-accounting iteration set. - OAK-T9.002 (Flash-Loan-Enabled Exploit) as the working-capital precondition that scaled the per-iteration extraction to nine figures.
- OAK-T7.001 (Mixer-Routed Hop — proceeds were laundered through Tornado Cash).
No public named-individual attribution. Pre-event funding routed through Tornado Cash; post-event laundering also routed through Tornado Cash.
Summary
Penpie is a yield-boosting / liquid-locking protocol built on top of Pendle Finance — an Ethereum-and-Arbitrum yield-tokenisation protocol that splits yield-bearing assets into Standardized Yield (SY), Principal Token (PT), and Yield Token (YT) components and trades them in dedicated AMM markets. Penpie deposits Pendle LP tokens on behalf of its users, claims the resulting Pendle-side rewards, and redistributes them. The composability surface — Penpie's reward-accounting code reads from and calls into Pendle markets it does not itself deploy — is the load-bearing structural fact for this incident.
On 2024-09-03, an attacker exploited a reentrancy vulnerability in Penpie's PendleStakingBaseUpg contract — specifically in the batch-reward-harvest path (batchHarvestMarketRewards / _harvestBatchMarketRewards), which iterated over registered Pendle markets and called each market's reward-claim entrypoint. Because Penpie had introduced permissionless Pendle-market registration in May 2024 (registerPenpiePool on PendleMarketRegisterHelper), the attacker was able to register a Pendle market that they themselves controlled — built around a malicious SY token contract — into Penpie's reward iteration.
When _harvestBatchMarketRewards called into the malicious market, the attacker-controlled SY contract re-entered Penpie's depositMarket path during the same outer transaction, against Penpie's mid-update reward-accounting state. The re-entries repeatedly inflated the deposit balance attributed to the attacker before Penpie's per-market accounting was finalised. Flash-loaned working capital was used to make each re-entered deposit economically meaningful. The attacker harvested the inflated rewards, repaid the flash loan, and exited.
Three transactions on Ethereum drained ~$26.7M; a follow-on transaction on Arbitrum extracted ~$621K of gUSDC after Pendle had paused on Ethereum but before Arbitrum was paused. Forensic write-ups were published by Halborn, SlowMist, Three Sigma, BlockApex, SolidityScan, and Rekt; Penpie/Magpie released a post-mortem; all converged on the same root-cause description.
Timeline (UTC)
| When | Event | OAK ref |
|---|---|---|
| 2024-05 | Penpie introduces permissionless Pendle-market registration via PendleMarketRegisterHelper.registerPenpiePool; trust boundary moves from "owner-curated markets" to "any market registered on Pendle Finance" |
T9.004 surface opened |
| Pre-event | Attacker funds deployment wallet via Tornado Cash withdrawal | (off-OAK pre-event observation) |
| Pre-event | Attacker deploys malicious SY-token contract | T9.004 setup |
| Pre-event | Attacker calls PendleYieldContractFactory.createYieldContract to mint matching PT/YT around the malicious SY |
T9.004 setup |
| Pre-event | Attacker calls PendleMarketFactoryV3.createNewMarket to wrap the malicious SY in a Pendle market |
T9.004 setup |
| Pre-event | Attacker calls registerPenpiePool to register the malicious Pendle market into Penpie's iteration set |
T9.004 execution |
| 2024-09-03 18:23 | First attack tx on Ethereum: flash loan acquired; depositMarket called on the malicious market; batchHarvestMarketRewards iterates into the malicious market; malicious SY contract re-enters depositMarket against mid-update reward state; rewards harvested; flash loan repaid; ~$15.7M extracted |
T9.005 + T9.002 extraction |
| 2024-09-03 18:23–18:42 | Two further Ethereum attack transactions repeating the pattern; cumulative ~$26.7M drained across three txs | T9.005 repeat |
| 2024-09-03 18:45 | Pendle pauses contracts on Ethereum (~3 minutes after the final Ethereum attack tx); ~$70M of further drainable assets protected on the Ethereum deployment | (defender response) |
| 2024-09-03 19:14 | Attacker executes the same pattern on Arbitrum; ~$621K gUSDC drained — the Arbitrum deployment had not yet been paused | T9.005 (Arbitrum leg) |
| 2024-09-03 (later) | Arbitrum deployment paused | (defender response) |
| 2024-09-04 | Penpie publicly offers white-hat negotiation: no legal action, no identity revelation in exchange for return of funds | (recovery attempt) |
| 2024-09-06 | Public 10% bounty (~$2.7M) posted; attacker does not respond | (recovery attempt) |
| 2024-09-04 onward | Attacker laundering through Tornado Cash begins; ~5,600 ETH within ~12 hours; remainder over the following week | T7.001 (off this example's framing) |
What defenders observed
Cross-function-via-cross-protocol reentrancy is the load-bearing teaching point of this incident. Penpie's reward-claim path called into a Pendle market — a contract Penpie did not deploy and did not control — before finalising Penpie's own per-market accounting. The attacker did not need to find a missing check on Penpie's side in the canonical 2016 sense; they needed to insert a contract under their control into the set of contracts Penpie iterated over, and use that contract's execution-flow control to re-enter Penpie's accounting path mid-update. A standard
ReentrancyGuardon Penpie's reward-claim function alone would not have prevented this, because the re-entry is not back into the same outer function — it is into a sibling state-mutating function (depositMarket) that the outer function's lock did not span. The mitigation requires either (a) a single lock that spans the set of Penpie functions sharing the reward-accounting state surface, or (b) checks-effects-interactions discipline that finalises Penpie's reward-accounting writes before any external call to a market contract, including markets the protocol does not own. Penpie's deployed code did neither.Permissionless integration changed the trust boundary, and the audit set did not move with it. The May 2024 introduction of permissionless
registerPenpiePoolwas the move from "Penpie's reward iteration set is curated by the owner" to "Penpie's reward iteration set is anyone-with-a-Pendle-market". All Pendle Market registrations on Penpie had been restricted to the contract owner at the time of the initial audits — the audits' implicit reentrancy threat-model assumed a curated registration set, in which the protocol controlled the contracts it iterated over. The audit work that had certified the earlier owner-curated design did not re-cover the new trust boundary, because audits scoped to a code snapshot do not automatically re-trigger when the integration model changes underneath them. Defenders should treat any permissionless-registration change as a re-audit trigger for every accounting path that iterates over the registered set.Pause latency mattered, and multi-chain pause is its own primitive. Pendle's Ethereum pause came ~3 minutes after the final Ethereum attack transaction — fast by industry standards. It did not propagate to Arbitrum in time to prevent the ~$621K Arbitrum follow-on at 19:14 UTC. Multi-chain deployments with shared trust assumptions need synchronised pause primitives, not per-chain manual pause sequences.
The on-chain attack signature is identifiable. Each attack transaction's internal call trace shows Penpie's reward-claim path calling into the attacker-controlled market contract, which calls back into Penpie's
depositMarketbefore the outer call returns — the canonical T9.005 internal-trace shape. The added twist for cross-protocol cases: the re-entered selector is on the target protocol itself, but reached through a third contract (the attacker-controlled market) that the outer function called rather than through the outer function's direct caller. Runtime monitoring at the protocol level (BlockSec PhalconHQ, Forta, OpenZeppelin Defender) can flag this pattern; pre-deployment static analysis catches it only if the analyser models the cross-contract call edges.
What this example tells contributors writing future Technique pages
T9.005 is not "solved by ReentrancyGuard." This is the load-bearing point of the Penpie example for OAK v0.1. The DAO 2016 case — the canonical T9.005 historical reference — is the same class of failure (surrendering execution flow before finalising state) but a different sub-pattern. ReentrancyGuard substantially closes the 2016 single-function pattern. It does not close cross-function reentrancy where the lock does not span the shared-state surface (Penpie's case), cross-protocol reentrancy where re-entry happens via a peer contract the protocol calls (also Penpie's case in the broader framing), hook-based reentrancy where the external call is implicit in a token-standard hook (Lendf.Me 2020), or read-only reentrancy where no state mutation occurs in the re-entry but a third protocol's view-function read is the loss surface. T9.005 worked examples should make this multi-variant structure explicit and avoid implying that a
nonReentrantmodifier is a sufficient mitigation in 2024-and-later codebases.The composability surface is the audit surface. Penpie's T9.005 surface is not visible by reading Penpie's contracts in isolation. It is visible by reading Penpie's reward-claim path together with the Pendle market interface it calls into and the
registerPenpiePooltrust assumption that determines who supplies the markets in that iteration set. Worked examples for protocols built atop other protocols (Penpie atop Pendle, Convex atop Curve, Aura atop Balancer, etc.) should make the cross-protocol call edges explicit in the timeline and observation sections rather than treating the upstream protocol as an opaque dependency.A T9.004 precondition is often required to make a T9.005 extraction reachable. The reentrancy was the extraction; the permissionless registration was the precondition. Without the precondition, the attacker could not have inserted the malicious market into Penpie's iteration set in the first place, and the reentrancy bug would have remained latent. Worked examples should distinguish the precondition Technique from the extraction Technique, the same way the Euler example at
/examples/2023-03-euler-finance.mddistinguishes T9.002 precondition from T9.004 extraction and the Beanstalk example at/examples/2022-04-beanstalk.mddistinguishes T9.002 + T9.003. Single-Technique framings flatten the lesson.Recovery is rare; the base rate is total loss. Penpie offered a 10% bounty; the attacker laundered through Tornado Cash instead. Worked examples should not let the Euler full-recovery outcome anchor expectations for T9-class incidents. Penpie is the more representative outcome.
Public references
- Penpie / Magpie — Post-Mortem Report — protocol-side post-mortem.
- Halborn — Explained: The Penpie Hack (September 2024) — function-level walkthrough and root-cause analysis.
- SlowMist — Incident Analysis: Penpie Attack — internal-trace-level forensic write-up.
- Three Sigma — Auditing the $27M Reentrancy Exploit — audit-perspective analysis emphasising the permissionless-registration trust-boundary change.
- BlockApex — Penpie Hack Analysis — function-by-function call-trace analysis.
- SolidityScan — Penpie Hack Analysis — static-analysis perspective on the missing reentrancy gating.
- Rekt — Penpie — public-facing incident summary.
- Vladislav Yaroshuk — How to Lose $600k After Pausing a Protocol — Arbitrum-leg analysis explaining the ~$621K follow-on after the Ethereum pause.
[zhou2023sok]— academic taxonomy classifying this sub-pattern as cross-function / cross-protocol reentrancy.[daoreentrancy2016retrospective]— historical T9.005 reference, contrasted here as a different sub-pattern of the same class.[halbornpenpie2024],[slowmistpenpie2024],[threesigmapenpie2024],[blockapexpenpie2024],[solidityscanpenpie2024],[pendlepostmortem2024],[rektpenpie2024]— Penpie-specific forensic write-ups (proposed citation keys; seecitations.bib).
Discussion
Penpie is OAK's canonical modern-variant T9.005 case for v0.1, sitting alongside The DAO (2016) as the historical reference and Cream Finance Iron Bank (2021) as the prior modern reference. The three together cover the variant space the technique page documents:
- The DAO is single-function reentrancy in the pre-
ReentrancyGuardera — the same path is re-entered before its state writes complete. - Cream Finance Iron Bank is cross-protocol reentrancy chained with flash loans against a peer protocol the attacker did not control — the attacker chained an external integration whose callback re-entered Cream while its internal state was mid-update.
- Penpie is cross-function-and-cross-protocol reentrancy where the attacker constructed the peer contract that the target iterated over, by exploiting a permissionless-registration entrypoint to insert their own contract into Penpie's iteration set.
The Penpie case is pedagogically valuable specifically because the obvious-looking mitigation — "add nonReentrant to _harvestBatchMarketRewards" — does not close the surface: the re-entry path is into depositMarket, a sibling function, and a per-function lock does not span the shared-state surface unless explicitly designed to. The structurally correct mitigations are:
- A shared lock across every Penpie function that mutates the per-market reward-accounting state, applied at the state-surface granularity rather than per-function.
- Checks-effects-interactions discipline that finalises Penpie's accounting writes before any external call to a market contract, regardless of provenance — including markets the protocol itself deployed, because trust-boundary regressions across upgrades are a known recurring class.
- Treating the set of registered markets as an attacker-controlled input under permissionless registration, and gating it with a market-validity check that constrains what the attacker can supply.
As of 2024–2025, post-Penpie audit checklists at top-tier DeFi protocols routinely include "permissionless-registration changes trigger re-audit of every iterating accounting path" as an explicit item, and the Penpie incident is the principal reason — the same dynamic visible after Euler for post-condition uniformity, where the standing audit pattern improved and the attack surface contracted as a direct downstream effect of a high-profile incident.
T9.005 remains Maturity = stable rather than historical for exactly this reason: the underlying failure mode — surrendering execution flow before finalising state — keeps reappearing in variants the canonical mitigation does not address, and Penpie is the September-2024 datapoint that closes the argument. Contributors writing future T9.005 worked examples or technique-page updates should treat ReentrancyGuard as a partial mitigation that closes the 2016 sub-pattern, and should expect the next high-profile T9.005 incident to surface in another sub-pattern the standard guard does not span — most plausibly read-only reentrancy, which is increasingly cited as a distinct sub-class in 2023–2025 audit-firm literature and may warrant its own technique ID in a v0.x update.