On May 13, 2026 at 23:22:02 UTC (BNB Chain block `98134017`), attacker EOA `0xcb26b3a469c5aee911d059a25de2b26ed52826e9` executed transaction `0x2fdd6aef515fb06ce803c55086bb71de712631979809c135cf6d02be133f5cdb`, which deployed bootstrap contract `0x8aa9cb61885121448f1bf9a5df80ec36c6fbd535` and executor `0xe812f2e6cdffdfa4ca496db0716a53301c37b705`. The attacker used Moolah proxy `0x8f73b65b4caaf64fba2af91cc5d4a2a1318e5d8c` as an unsafe flash-loan callback entrypoint, then composed nested flash loans, a large USDT borrow, and a deep Pancake/Vault routing path before unwinding the whole position. The transaction finishes with the attacker EOA netting `54,598.222194166280831143 USDT`, while the Mai1/USDT liquidity path at `0xa0e4b7ade986004112a49d79fc1f8e27df4c1e03` ends down `62,544.672240586975276114 USDT` and `127,438,133.706326618655250561 Mai1`. The primary root cause is reentrancy through `Moolah.flashLoan(address,uint256,bytes)`, with flash-loan abuse as the execution technique.

## Root Cause

### Vulnerable Contract

– **Proxy**: `MoolahProxy` at `0x8f73b65b4caaf64fba2af91cc5d4a2a1318e5d8c`
– **Implementation**: `Moolah` at `0x9321587ea0dc8247f8f03e8696c047b2713bb79a`
– **Proxy status**: verified upgradeable proxy; the proxy delegates `flashLoan` into the verified `Moolah.sol` implementation
– **Source type**: verified Solidity source in `0x9321587ea0dc8247f8f03e8696c047b2713bb79a/src/moolah/Moolah.sol`

### Vulnerable Function

– **Function**: `flashLoan(address,uint256,bytes)`
– **Selector**: `0xe0232b42`
– **Implementation file**: `src/moolah/Moolah.sol`
– **Callback interface**: `IMoolahFlashLoanCallback.onMoolahFlashLoan(uint256,bytes)` in `src/moolah/interfaces/IMoolahCallbacks.sol`

### Vulnerable Code

“`
function flashLoan(address token, uint256 assets, bytes calldata data) external whenNotPaused { require(!flashLoanTokenBlacklist[token], ErrorsLib.TOKEN_BLACKLISTED); require(assets != 0, ErrorsLib.ZERO_ASSETS); emit EventsLib.FlashLoan(msg.sender, token, assets); IERC20(token).safeTransfer(msg.sender, assets); IMoolahFlashLoanCallback(msg.sender).onMoolahFlashLoan(assets, data); // <– VULNERABILITY IERC20(token).safeTransferFrom(msg.sender, address(this), assets); // repayment only checked after callback }
“`

The callback target is explicitly exposed by the verified interface:

“`
interface IMoolahFlashLoanCallback { function onMoolahFlashLoan(uint256 assets, bytes calldata data) external; }
“`

### Why It’s Vulnerable

**Expected behavior:** a flash-loan entrypoint should either prevent reentrancy entirely or ensure the protocol cannot be driven through additional privileged state transitions before repayment is enforced.

**Actual behavior:** `flashLoan()` transfers assets out, yields control to an attacker-controlled callback, and only then attempts to pull repayment back. Unlike nearby stateful entrypoints such as `supply()`, `withdraw()`, `borrow()`, and `repay()`, this function is not protected by `nonReentrant`. The trace shows the executor entering the proxy-level `flashLoan()` twice ( `decoded_calls.json` indices `18` and `22`) before the outer loan is settled; the four `0xe0232b42` appearances are two executor-to-proxy calls plus two proxy-to-implementation delegatecalls ( `18/19` and `22/23`). That callback window let the attacker borrow additional capital, route it through `supply`, `borrow`, `lock`, `take`, `sync`, and `settle`, and still repay Moolah before the function returned.

## Attack Execution

### High-Level Flow

1. The transaction is itself a contract-creation transaction from attacker EOA `0xcb26…26e9`; receipt metadata shows bootstrap contract `0x8aa9…d535` as `contractAddress`, and the trace shows that bootstrap contract creating executor `0xe812…b705`.
2. The executor calls `MoolahProxy.flashLoan()` for WBNB at decoded call `18`, which delegates into the implementation at `19` and calls back into attacker-controlled `onMoolahFlashLoan()` at `21`.
3. Inside that callback, the executor calls `MoolahProxy.flashLoan()` again for USDT at decoded call `22`, which delegates at `23` and reenters attacker code again at `25`.
4. Using the callback window, the executor routes funds through the Aave-style pool proxy `0x6807dc923806fe8fd134338eabca509979a7e0cb`, including `supply(address,uint256,address,uint16)` at `225` and `borrow(address,uint256,uint256,uint16,address)` at `238`.
5. The exploit path then enters `0x238a358808379702088667322f80ac48bad5e6c4` with `lock(bytes)` at `268`, receives `lockAcquired(bytes)` at `269`, and executes `take(address,address,uint256)` at `270`.
6. The deep middle of the trace contains repeated Pancake flash-callback hops ( `pancakeV3FlashCallback(uint256,uint256,bytes)`) and downstream Mai1/pair interactions. The Mai1 token path itself later calls `sync()` on the Mai1/USDT pair at decoded call `474`, showing that the final profit realization occurs on the MAIL/Mai1 liquidity side, not from an unrepaid Moolah balance.
7. During unwind, the executor repays the intermediate routing layer with `repay()` at `636/637`, withdraws with `withdraw()` at `650/651`, then finalizes the `0x238a…e6c4` path with `sync(address)` at `689` and `settle()` at `692`.
8. `funds_flow.json` credits the final attacker EOA with `54,598.222194166280831143 USDT`, while Moolah ends the transaction flat on the flash-loaned USDT/WBNB balances.

### Trace Anchors

Decoded call indexMeaningFirst user-facing Nested user-facing

## Financial Impact

`funds_flow.json` is the primary source for the profit calculation and address-level balance changes.

Address / componentNet changeNotesAttacker EOA Final realized attacker gain reported by Executor effectively flat on USDT, tiny WBNB dustTemporary capital was recycled and repaid inside the same transactionMoolah proxy no lasting USDT/WBNB lossFlash-loaned balances return by the end of executionMai1/USDT path Permanent downstream liquidity loss

Temporary capital was much larger than the final profit:

– Moolah transfers `424,107.146731444623695429 WBNB` to the executor at transfer log `53`
– Moolah transfers `7,265,733.22110355069872967 USDT` to the executor at transfer log `55`
– The Venus vUSDT market transfers `95,247,564.226904911099771844 USDT` to the executor at transfer log `68`

Those large transient balances are important context: the `54.6k USDT` figure is the final net attacker gain, not the gross amount routed through the exploit path.

## Evidence

RoleAddressEvidenceAttacker EOABootstrap contractAttack executorCreated and called by bootstrap in Vulnerable proxyUser-facing Vulnerable implementationDelegatecall target at decoded calls Secondary routing pool proxyHandles Secondary routing pool implementationVerified Vault/adapter pathHandles MAIL / Mai1 tokenAppears in final negative attacker net token flow and pair Mai1/USDT pairEnds with the largest permanent liquidity loss in

Additional transaction facts:

– **Transaction hash**: `0x2fdd6aef515fb06ce803c55086bb71de712631979809c135cf6d02be133f5cdb`
– **Chain**: BNB Chain ( `56`)
– **Block**: `98134017`
– **Timestamp**: `2026-05-13T23:22:02Z`
– **Status**: success ( `receipt.status = 0x1`)
– **Gas used**: `0x73ef85`( `7,597,957`)

## Remediation

1. Add reentrancy protection to `flashLoan()` or restructure it so control is not yielded to an attacker-controlled callback before repayment is enforced.
2. Treat flash-loan callbacks as privileged cross-protocol execution points and constrain what can happen before the loan is considered settled.
3. Add invariant tests that explicitly forbid nested `flashLoan()` callback recursion from composing new borrow / settlement paths before the outer flash loan returns.
4. Add integration tests that model callback-driven compositions with secondary lending pools and vault locks rather than testing `flashLoan()` in isolation.

## Artifacts

– `tx.json`, `receipt.json`
– `trace_callTracer.json`, `trace_prestateTracer.json`
– `decoded_calls.json`, `selectors.json`
– `funds_flow.json`
– Verified Moolah source under `0x9321587ea0dc8247f8f03e8696c047b2713bb79a/`
– Verified pool implementation under `0x00d1397960aa97f694e41c3632b74c151a00c33b/`