Calculated from recorded token losses using historical USD prices at the incident time.
0xb983e01458529665007ff7e0cddecdb74b967eb6Ethereum0xd8ee69652e4e4838f2531732a46d1f7f584f0b7fEthereumAn unprivileged Ethereum account exploited bZx Fulcrum's iETH market by repeatedly calling transfer with itself as both sender and receiver on the iETH proxy 0xb983e01458529665007ff7e0cddecdb74b967eb6. Because the live delegated implementation mishandled self-transfers, each call increased the attacker's iETH balance without adding backing assets. The attacker then redeemed the inflated iETH through public burnToEther(address,uint256) calls and withdrew 9308128184294509434058 wei of ETH-equivalent value from the market. The root cause is a concrete accounting bug in the active LoanTokenLogicWeth implementation, not a privileged action or an uncertain economic theory.
Fulcrum iTokens are share tokens over lending-pool assets. Holders can mint iETH by depositing ETH and can later redeem shares through burn or burnToEther, with payout determined by tokenPrice(). The iETH market is exposed through proxy 0xb983e01458529665007ff7e0cddecdb74b967eb6, which forwards user calls via delegatecall to a mutable implementation. That design means the active implementation code, not the proxy shell, defines the live transfer and redemption semantics.
The collected source for the active implementation shows LoanTokenLogicWeth at 0xde744d544a9d768e96c21b5f087fc54b776e9b25 exposing both mintWithEther and as public entrypoints. The same source also contains , which performs ERC20 balance bookkeeping. The exploit matters because iETH balances are redeemable economic claims on the pool; any balance inflation bug can be converted into direct ETH loss when redemptions remain open.
0xd8906a8ab71b5c772a07f301ccb7fa539cdce03a84d88e806c8de7bc5581a6cd0x783330d73a462e0a32b9b46ff139c3d6d5ff7e191746dc0f6a0b8bcffb0bcbb80xbbfc69cb015b52f1e1ca8524a2452e80db4073983b73af796e0b01d147bf72d70x1b4fc6de962c97944bfc33462b334f4c418fba4d54f3edea0ae3dc9fa3b1faf60x1dcc24254a05a44f8bcedfb0673238c3ab4ab4e09555fee2370b327fb24102300x54e45ce9b037a6e353284533958147a607ff0569670d62add99d5f5f3b9e09e90x7b1d124f2978e974bdd6453b8e6c0235184b203c4264bfa316ddddf523bbb7eb0xc312ca7cfc23ac8e78e2fddebcc44b4cb0a3daa91267efc0367508e9caf434290x55e13687db5ff0dc80726caf99ce8868915006a9ff926b4842c6afa36c5e74ce0x72cb33ec058aad0e61cac5771dc96b4e37f12cf9ea210113bc9df67bf42e49b30xd862859e20a9d914c82a2646e1155e7fa123cfe0b5f718970ea02a93c477ff71burnToEther_internalTransferFromThe vulnerability is a self-transfer accounting bug in the active iETH implementation. In _internalTransferFrom, the contract reads balances[_from], writes the debited value back to storage, then separately reads balances[_to] and writes balances[_to] + _value. That logic is safe only when _from != _to. When the sender transfers to itself, both writes target the same storage slot, so the second write restores the stale pre-debit balance and then adds _value, turning a no-op transfer into balance inflation.
The relevant invariant is straightforward: an ERC20 self-transfer must preserve the holder's balance and must not create new economic claim on pool assets. The breakpoint is the stale _balancesTo read and subsequent balances[_to] = _balancesTo.add(_value) write in _internalTransferFrom. Because burnToEther later converts iETH shares into ETH using the live token price, the attacker can cash out these unbacked shares against real pool liquidity.
The source code collected for the active implementation contains the vulnerable transfer logic:
function _internalTransferFrom(
address _from,
address _to,
uint256 _value,
uint256 _allowanceAmount)
internal
returns (bool)
{
uint256 _balancesFrom = balances[_from];
uint256 _balancesFromNew = _balancesFrom.sub(_value, "16");
balances[_from] = _balancesFromNew;
uint256 _balancesTo = balances[_to];
uint256 _balancesToNew = _balancesTo.add(_value);
balances[_to] = _balancesToNew;
}
When _from == _to, both balances[_from] and balances[_to] refer to the same slot. The first store debits the balance, but the second calculation still uses the stale pre-debit value loaded into _balancesTo, so the final store sets the slot to old_balance + value. For a self-transfer where value == old_balance, the balance doubles.
The seed exploit trace confirms that the proxy delegates the transfer to the live implementation:
0xB983E01458529665007fF7E0CDdeCDB74B967Eb6::transfer(
0xd1c0f1316140D6bF1a9e2Eea8a227dAD151F69b7,
199175731349382745647
)
└─ 0xdE744d544A9d768e96C21B5F087Fc54b776E9b25::transfer(...) [delegatecall]
The same trace shows a Transfer event where source and destination are the same attacker address, followed by storage updates to the attacker's balance slot. The collector's balance diff for transaction 0x85dc2a433fd9eaadaf56fd8156c956da23fc17e5ef83955c7e2c4c37efa20bb5 records the attacker's iETH balance changing from 199175731349382745647 to 398351462698765491294, a delta exactly equal to the transfer amount, while no legitimate mint path was invoked.
After the attacker inflated the iETH balance through nine public self-transfers, the attacker used the public redemption path:
function burnToEther(
address receiver,
uint256 burnAmount)
external
nonReentrant
returns (uint256 loanAmountPaid)
{
loanAmountPaid = _burnToken(burnAmount);
if (loanAmountPaid != 0) {
IWethERC20(wethToken).withdraw(loanAmountPaid);
Address.sendValue(receiver, loanAmountPaid);
}
}
This function converts the inflated iETH shares into real ETH by burning shares and withdrawing underlying WETH from the market. That is the end-to-end mechanism that turns an internal accounting error into realized pool loss.
The adversary-controlled EOA 0xd1c0f1316140d6bf1a9e2eea8a227dad151f69b7 first held 199175731349382745647 iETH worth roughly 200.000205513196261625 ETH-equivalent before the exploit sequence. Starting at block 10852721, it submitted a series of public self-transfers on the iETH proxy. The first transaction was 0x85dc2a433fd9eaadaf56fd8156c956da23fc17e5ef83955c7e2c4c37efa20bb5, followed by eight more self-transfers that repeatedly doubled the balance until the account held 101977.974450883965771264 iETH.
The attacker then submitted four public burnToEther transactions:
0xc312ca7cfc23ac8e78e2fddebcc44b4cb0a3daa91267efc0367508e9caf43429
0x55e13687db5ff0dc80726caf99ce8868915006a9ff926b4842c6afa36c5e74ce
0x72cb33ec058aad0e61cac5771dc96b4e37f12cf9ea210113bc9df67bf42e49b3
0xd862859e20a9d914c82a2646e1155e7fa123cfe0b5f718970ea02a93c477ff71
Across those redemptions, the attacker withdrew 9308128184294509434058 wei of ETH-equivalent value from the market. Follow-up ETH transfers from the exploiter EOA to 0xe925535a79544d86cf587a4117351d56c6a377d8 show downstream profit distribution inside the adversary cluster, but those transfers are not required to realize the ACT opportunity itself. The ACT path is complete using only public iETH mint, transfer, and burn entrypoints.
The direct measurable loss is 9308128184294509434058 wei of ETH-equivalent value withdrawn from the iETH market. The attacker paid 0.193909147212674048 ETH in gas across the exploit sequence and still increased position value from 200.000205513196261625 ETH-equivalent to 9308.128184294509434058 ETH-equivalent, for a net gain of 9107.934069634100498385 ETH-equivalent relative to the pre-attack position.
The affected public components are the Fulcrum iETH proxy 0xb983e01458529665007ff7e0cddecdb74b967eb6, its active implementation 0xde744d544a9d768e96c21b5f087fc54b776e9b25, and the broader bZx protocol context anchored at 0xd8ee69652e4e4838f2531732a46d1f7f584f0b7f. The security failure is that redeemable share balances became creatable without backing collateral.
0x85dc2a433fd9eaadaf56fd8156c956da23fc17e5ef83955c7e2c4c37efa20bb5 metadata and trace.199175731349382745647 to 398351462698765491294 in the seed self-transfer.LoanTokenLogicWeth showing _internalTransferFrom, mintWithEther, and burnToEther.root_cause.json, including nine self-transfers and four burnToEther redemptions.