Worked example · 2026-06
Joe Agent ($JOE) — _removeLiquidityViaContract single-function reentrancy — 2026-06
Summary
Joe Agent ($JOE) is a project on BNB Chain whose liquidity-management contract exposed _removeLiquidityViaContract, a function that returns a user's pooled BNB when they withdraw their LP position. The function performed the BNB return as a low-level call to the recipient before it decremented the user's recorded LP amount (lpInfo[user].lpAmount).
In early June 2026, an attacker deployed a contract that called _removeLiquidityViaContract and, in the receiving fallback triggered by the BNB transfer, re-entered the same function while lpInfo[user].lpAmount still reflected the pre-withdrawal balance. Each re-entry passed the (stale) accounting check and released another tranche of BNB. Looping ~25 times in a single transaction, the attacker extracted ~62.5 BNB and ~1,196,000 JOE — approximately $45,000 — before the call stack unwound and the state was finally (and now incorrectly) updated.
The root cause is a checks-effects-interactions ordering violation: the contract interacted with an untrusted external party before finalising its own state. No flash loan, no oracle, no governance, and no key compromise were involved — the call-graph shape (one external transaction whose internal trace repeatedly re-enters the same selector before returning) is the complete diagnosis.
Why this is structurally significant
T9.005 (Reentrancy) decomposes into single-function, cross-function, cross-protocol, read-only, and hook-based sub-shapes. Joe Agent is the purest single-function form — the same shape as The DAO (2016) and the simplest case in the class. Its presence in the 2026 corpus is significant precisely because it is not novel:
- The corpus's modern reentrancy anchors (Cream/Iron Bank cross-protocol, Sentinel read-only, Curve/Vyper compiler-level) exist because simple per-function
ReentrancyGuardis no longer sufficient against advanced variants. Joe Agent is the reminder that the basic variant — which the guard fully prevents — still ships unguarded on low-cost chains. - The realised loss (~$45K) is small, which is itself a data point: single-function reentrancy on a small BNB-Chain project caps out at the contract's own balance. The class's large losses (DAO ~$60M, Cream ~$130M) come from scale and cross-protocol leverage, not from the mechanism being more sophisticated.
Joe Agent therefore earns its place as a low-stakes, high-clarity teaching anchor: the mechanism is unambiguous, the fix is one line of ordering or one modifier, and the lesson is about audit coverage on cheap chains rather than about a new attack primitive.
Timeline (UTC)
| When | Event | OAK ref |
|---|---|---|
| Pre-2026-06 | Joe Agent's _removeLiquidityViaContract returns BNB via low-level call to the recipient before decrementing lpInfo[user].lpAmount (checks-effects-interactions violation; no reentrancy guard) |
(standing T9.005 surface) |
| 2026-06 (early) | Attacker calls _removeLiquidityViaContract; during the BNB transfer the attacker contract re-enters the function against stale lpAmount, looping ~25 times in one transaction |
T9.005 execution |
| 2026-06 (early) | Attacker extracts ~62.5 BNB and |
T5 outflow |
| 2026-06 (early) | Incident reported amid the early-June BNB-Chain cluster | (third-party detection) |
What defenders observed
- Pre-event (code layer):
_removeLiquidityViaContractplaced an external low-level call before the state-update (lpInfo[user].lpAmount) and carried nononReentrantguard. This is mechanically detectable: static analysers (Slither'sreentrancy-eth/reentrancy-no-eth) flag external-call-before-state-write on a balance-bearing path as a hard finding. Defender lesson: any function that sends native value before zeroing the corresponding internal balance is a reentrancy finding by inspection. - At-event (on-chain signal): the exploitation signature is the call trace — a single transaction whose internal trace re-enters the same function selector ~25 times before the originating call returns. A mempool/trace monitor asserting "no function re-enters itself within one transaction on a withdrawal path" would have flagged it.
- Fix: apply checks-effects-interactions (decrement
lpInfo[user].lpAmountbefore the BNB transfer) and add anonReentrantmodifier to the withdrawal path. Either change alone breaks the exploit; both is standard practice.
What this example tells contributors writing future Technique pages
- Keep a pure single-function anchor in the T9.005 set. Modern reentrancy anchors emphasise cross-protocol and read-only variants because those defeat naive guards; Joe Agent preserves the baseline case where a single
nonReentrantmodifier (or correct ordering) is a complete fix. Contributors should record the sub-shape (single-function vs cross-function vs cross-protocol vs read-only vs hook) on every new reentrancy case. - Small realised losses are still canonical. A ~$45K single-function reentrancy is a better teaching artefact than a $100M cross-protocol chain, because the mechanism is uncontaminated by flash loans, oracles, or composability. Record it as such.
- Audit-coverage-on-cheap-chains is the recurring meta-lesson. The 2026 appearance of a 2016-vintage bug on BNB Chain is a process finding, not a technical one: the tool that catches this (Slither) is free and the pattern is decade-documented. Note this in the defender section of low-cost-chain reentrancy cases.
Public references
[bitgetjunehacks2026]— Bitget News, "Exploit hits Gnosis Pay, TesseraDAO loses $2.5M as June hacks start to climb" — describes the Joe Agent ($JOE)_removeLiquidityViaContractreentrancy: BNB sent via low-level call before updatinglpInfo[user].lpAmount, ~25 reentrancy loops, ~62.5 BNB +1.196M JOE ($45K). (Bitget mirror, original via Cryptopolitan): https://www.cryptopolitan.com/exploit-hits-gnosis-pay-tesseradao-june/- Solidity documentation / Slither — checks-effects-interactions pattern;
reentrancy-eth/reentrancy-no-ethdetectors for external-call-before-state-write. - Cross-reference:
examples/2016-06-the-dao.md(the canonical single-function reentrancy original) andexamples/2020-04-lendf-me.md(hook-based variant) in the T9.005 set.
Discussion
Joe Agent is a deliberately low-stakes addition to OAK's reentrancy series — its value is clarity, not scale. Where Cream Finance (examples/2021-10 cohort) anchors cross-protocol reentrancy and the reason simple guards are insufficient against advanced variants, Joe Agent anchors the opposite end: the original single-function shape that a guard fully prevents, still shipping unguarded on a low-cost chain in 2026. The pairing is the lesson — the reentrancy class spans from "one modifier fixes it" (Joe Agent) to "no single guard suffices" (Cream/read-only), and contributors should locate every new case on that spectrum. For Joe Agent specifically, the meta-finding is about audit economics on cheap chains: the bug is decade-old and free to detect, and its recurrence reflects thin audit coverage on low-cost-deployment ecosystems rather than any advance in attacker capability.