Back to Blog

~$18M Lost: jaredFromSubway, Aztec & More | BlockSec Weekly

Code Auditing
June 25, 2026
11 min read
Key Insights

During the past week (2026/06/15 - 2026/06/21), we observed 3 notable security incidents with total losses of approximately $18.3M.

Date Incident Type Estimated Loss
2026/06/18 Aztec Improper Public Input Binding ~$2.2M
2026/06/20 LABUBU Token Misconfiguration ~$1.1M
2026/06/20 jaredFromSubway Improper Approval Management ~$15M
  • Aztec: Selected because the protocol was exploited a second time within three days, this time through its escape hatch circuit, highlighting recurring ZK proof binding issues.
  • jaredFromSubway: Selected because the MEV bot's contract granted approvals to untrusted token contracts without verifying consumption or revoking residual allowances, allowing the attacker to accumulate and drain ~$15M.

Best Security Auditor for Web3

Validate design, code, and business logic before launch

Weekly Highlight: jaredFromSubway

Unlike traditional approval exploits — where attackers abuse vulnerabilities in trusted DeFi contracts to drain assets that users approved to those contracts — this attack targets the reverse direction: the MEV bot proactively granted approvals on its own assets to untrusted third-party contracts as part of its arbitrage operations. The attacker constructed a fake trading environment (essentially a honeypot) where fake swap pools emitted real Swap and Sync events while the fake tokens never consumed the granted allowances, accumulating these outward-facing approvals before harvesting them, with reported total losses of ~$15M.

On June 20, 2026, jaredFromSubway, an MEV bot operator on Ethereum, lost approximately $15M [1]. Based on on-chain analysis, the root cause was improper approval management in the bot contract: approvals were granted to untrusted wrapper contracts that never consumed them, and the attacker accumulated these unconsumed allowances until draining the bot's real balances in a single transaction.

Background

jaredFromSubway is a well-known MEV bot operator on Ethereum, specializing in sandwich attacks and on-chain arbitrage. The victim contract (0x1f2f...f387) is one of its operating wallets, holding large working-capital balances in WETH, USDC, and USDT.

MEV bots of this kind must dynamically interact with arbitrary new tokens and pools that appear on-chain. They monitor the mempool, simulate transactions, and automatically approve token interactions to capture arbitrage opportunities. This operational model relies on the assumption that tokens behave as expected: when a swap is executed, the token contract consumes the granted allowance by calling transferFrom.

Vulnerability Analysis

The root cause is the MEV bot's improper approval management when interacting with untrusted contracts.

The bot executes various arbitrage paths across Uniswap pools and routers. In most interactions, the bot pushes tokens to pools directly via transfer, where the bot itself is msg.sender and no approval is needed. However, interactions with wrapper-style token contracts follow a pull model: the bot calls wrapper.wrapTo(), and inside that call the wrapper contract calls realToken.transferFrom(bot, wrapper, amount) to pull the bot's real tokens. Since msg.sender during transferFrom is the wrapper contract — not the bot — a prior approve is required:

  1. <real_token>.approve(tokenContract, amount) — grant allowance on the real token to the wrapper contract
  2. tokenContract.wrapTo() → multi-hop swap() across pools → tokenContract.unwrap() — wrap the real token, route through pools, and unwrap back to the real token

The bot assumed that wrapTo() would consume the allowance via transferFrom, as a well-behaved wrapper contract would. However, the bot never verified whether the allowance was actually consumed after the operation, nor revoked any residual allowance. If wrapTo() does not call transferFrom, the full allowance survives the operation and becomes a persistent attack surface — any contract holding such an allowance can later call transferFrom to move the bot's real assets.

Attack Analysis

Based on on-chain reconstruction, the attacker constructed a fake trading environment with three components to exploit the vulnerability described above:

  1. Fake wrapper tokens: Each fake token used the real token's name but prefixed the symbol with f (e.g., name USD Coin with symbol fUSDC for USDC). It implemented wrapTo() and unwrap() to mimic a legitimate wrapper, plus an attacker-restricted withdraw() function that drained unconsumed allowances via transferFrom.

  2. Fake swap pools: The attacker deployed approximately 44 Uniswap V2-style pools via a self-deployed factory. These pools paired fake tokens with each other to form convincing swap routes. When swap() was called, the pools emitted real Sync and Swap events indistinguishable from legitimate trades.

  3. Attacker-crafted profits: During unwrap(), the fake token sent a small amount of real tokens back to the bot via transfer. The bot received real profit, but it was deliberately crafted by the attacker rather than earned from market arbitrage.

The attacker controlled these components via a per-block getStatus() switch in an external contract. getStatus() returned 1 when called in the same block as an activation transaction (which set _getStatus = block.number), and 0 otherwise. When getStatus() == 0, wrapTo() called transferFrom normally and the allowance was consumed. When getStatus() == 1, wrapTo() skipped transferFrom — the allowance was not consumed — while unwrap() still returned attacker-crafted tokens to the bot. The attacker likely used builder bribes to place the activation transaction in the same block as the bot's transaction when they wanted to accumulate allowances.

The attack proceeded in three phases:

Phase 1: Deploy attack infrastructure

  • Step 1: The attacker set up the infrastructure across blocks 25354424 to 25354519. This included deploying a fake token factory contract (0x81f2...0091), creating ~44 fake Uniswap V2 pools via a self-deployed factory, funding the pools with initial token balances so swap() calls would succeed, and sending 0.01 ETH to the harvest contract (0xb84d...df52) for gas and builder bribe.

  • Step 2: The attacker mass-produced fake wrapper tokens via CREATE2, each mimicking a real token (using the real name but prefixing the symbol with f) and carrying an attacker-restricted withdraw() function. CREATE2 gave deterministic addresses that the harvest contract could loop over.

Phase 2: Build trust and accumulate approvals

  • Step 3 (initial trust): In the earliest transactions (e.g., 0x542d...362b at block 25354425), the fake tokens had no getStatus() switch — wrapTo() called transferFrom directly, consuming the allowance. The bot approved, wrapped, swapped, unwrapped, and profited normally. This established the fake tokens as profitable trading opportunities.

  • Step 4 (continued trust): In subsequent transactions (e.g., 0x085e...37e51), the getStatus() switch was deployed but returned 0 (different block from activation). wrapTo() still called transferFrom and consumed the allowance. The bot continued to profit and kept interacting.

  • Step 5 (accumulation): Starting from 0x8560...1915 at block 25360519, the attacker placed an activation transaction in the same block as the bot's transaction via builder bribe, causing getStatus() to return 1. In this mode, wrapTo() skipped transferFrom — the allowance was not consumed — but unwrap() still sent a small amount of real tokens back to the bot. The bot saw a profitable operation and left the approval in place. Over approximately 600 blocks (~13 transactions), the bot repeated this pattern across WETH, USDC, and USDT, accumulating unconsumed allowances on all three real assets.

Phase 3: Harvest

  • Step 6: The attacker called withdraw() on all fake tokens in the harvest transaction 0x2be870...cf3e65, using the unconsumed allowances to call transferFrom and move the bot's real balances to the attacker. A 0.01 ETH builder bribe was included to ensure block inclusion. The harvest extracted 1,474.58 WETH + 2,870,573 USDC + 2,035,760 USDT (~$7.5M) from the victim contract alone.

The identified attack transaction leads to ~$7.5M in losses, and the total loss is approximately $15M based on jaredFromSubway's claim [1].

Conclusion

The root cause of this incident was the MEV bot's improper approval management when interacting with untrusted contracts. Unlike traditional approval exploits where attackers abuse vulnerabilities in trusted DeFi contracts to drain user-approved assets, this attack works in the reverse direction: the bot proactively approved its own assets to untrusted third-party contracts as part of its arbitrage operations. The attacker accumulated these outward-facing, unconsumed approvals and harvested them in a single transaction.

To reduce similar risks in the future, bot contracts interacting with untrusted token contracts should verify that approvals are consumed after each operation and revoke any residual allowance. Unconsumed allowances after a successful-looking trade are a strong signal of malicious token behavior.

Get Started with Phalcon Explorer

Dive into Transactions to Act Wisely

Try now for free

More Incidents This Week

Aztec

On June 18, 2026, a missing equality constraint in Aztec's escape hatch ZK circuit allowed an attacker to withdraw approximately $2.2M (1,158 ETH, 150K DAI, and ~0.47 renBTC) from the legacy RollupProcessor contract on Ethereum [2], [3]. This was the second Aztec exploit in three days (the first, covered in our previous report, targeted the upgraded RollupProcessorV3) through a related ZK circuit public input binding bug.

Background

Aztec's legacy RollupProcessor includes an escapeHatch function: a safety mechanism that lets anyone submit a single-transaction proof when the rollup operator stops processing. Unlike processRollup (which requires an authorized provider), the escape hatch opens in periodic windows based on block number and is callable by anyone:

function escapeHatch(
    bytes calldata proofData,
    bytes calldata signatures,
    bytes calldata viewingKeys
) external override whenNotPaused {
    (bool isOpen, ) = getEscapeHatchStatus();
    require(isOpen, 'Rollup Processor: ESCAPE_BLOCK_RANGE_INCORRECT');
    processRollupProof(proofData, signatures, viewingKeys);
}

The escape hatch uses a dedicated ZK circuit (escape_hatch_circuit) that processes a join-split transaction: consuming input notes from the Merkle tree and creating output notes. The circuit must verify that the input notes exist in the current data tree (using old_data_root for Merkle membership), then expose the same root as a public input for the L1 contract to validate against on-chain state.

Vulnerability Analysis

The vulnerability is in the escape hatch circuit (escape_hatch_circuit.cpp). The old_data_root value is turned into two independent witnesses with no equality constraint connecting them.

The first witness (line 33) is passed into the join-split circuit component, where it is used for Merkle membership proofs to verify that input notes exist in the data tree:

join_split_inputs inputs = {
    // ...
    witness_ct(&composer, tx.js_tx.old_data_root),        // line 33: first witness
    // ...
};
auto outputs = join_split_circuit_component(composer, inputs);

The second witness (line 50) is created independently and exposed as a public input (line 88), which the Solidity contract extracts and checks against the on-chain data root:

auto old_data_root = field_ct(witness_ct(&composer, tx.js_tx.old_data_root));  // line 50: second witness
// ...
composer.set_public_input(old_data_root.witness_index);    // line 88: exposed as public input

In a ZK circuit, each witness_ct call creates an independent variable. Without an explicit assert_equal between lines 33 and 50, the prover can assign different values to these two witnesses. On the Solidity side, validateMerkleRoots checks require(oldDataRoot == dataRoot) using only the public input from line 50, with no visibility into the value used at line 33.

The same unbinding pattern also exists for input_owner and output_owner: these values are witnessed at lines 38–39 (passed into join_split_circuit_component for ownership verification) and again at lines 111–112 (exposed as independent public witnesses). However, we have not identified a practical exploit path for this gap.

Attack Analysis

The escape hatch circuit was removed from the aztec-connect codebase [4], but the deployed verifier contract still contains the EscapeHatchVk verification key, so proofs generated with the vulnerable circuit can still pass on-chain verification. At the time of the attack, the contract had been silent for 142 days and the escape hatch window was open. The attacker's address had been created just 14 hours before the exploit via Union Chain [2]. The attack consists of three core steps:

  • Step 1: The attacker constructed a fake Merkle tree containing self-owned notes of arbitrary value. These notes did not exist in the real on-chain data tree (the Merkle tree storing all valid notes).

  • Step 2: The attacker generated an escape hatch proof exploiting the unbound witnesses described above. Line 33's witness (used for Merkle membership in the join-split component) was set to the fake Merkle root (the membership check passed because the fabricated notes existed in the fake tree, and ownership checks passed because the attacker held the signing keys). Line 50's witness (exposed as the public input checked by Solidity) was set to the real on-chain data root (the Solidity require(oldDataRoot == dataRoot) check passed because this value matched the contract's stored root).

  • Step 3: With both the circuit and Solidity checks satisfied, the proof verified successfully. The contract processed the escape hatch transaction as legitimate and released the funds.

The attacker repeated this process across three transactions (0x9e1d6a...6b03ca, 0xab306c...59c2b5, 0x5c196c...4705c3), targeting different assets and extracting 1,158 ETH, 150K DAI, and ~0.47 renBTC respectively, totaling approximately $2.2M.

Conclusion

The root cause of this incident was a missing equality constraint between two witnesses for old_data_root in the escape hatch circuit. One witness was used for private note membership verification inside the join-split component, the other was exposed as the public input checked by Solidity. Without a constraint binding them, the attacker proved ownership of fabricated notes against a fake Merkle tree while the L1 contract saw a valid on-chain root. Notably, removing the vulnerable circuit from source did not neutralize the already-deployed verifier contract — the escapeHatch function on the legacy RollupProcessor remains callable whenever its block-number window is open.

To reduce similar risks in the future, when the same logical value appears at multiple points in a ZK circuit, all instances must be explicitly constrained to be equal — independent witness_ct calls for the same value are a binding gap. Circuit audits should systematically verify that every public input is bound to the circuit-internal value it represents.

Get Started with Phalcon Security

Detect every threat, alert what matters, and block attacks.

Try now for free

References

About BlockSec

BlockSec is a full-stack blockchain security and crypto compliance provider. We build products and services that help customers to perform code audit (including smart contracts, blockchain and wallets), intercept attacks in real time, analyze incidents, trace illicit funds, and meet AML/CFT obligations, across the full lifecycle of protocols and platforms.

BlockSec has published multiple blockchain security papers in prestigious conferences, reported several zero-day attacks of DeFi applications, blocked multiple hacks to rescue more than 20 million dollars, and secured billions of cryptocurrencies.

Sign up for the latest updates
Web3 Companion: The Open-Source Secure Agentic Wallet

Web3 Companion: The Open-Source Secure Agentic Wallet

BlockSec open-sources Web3 Companion, a security-first agentic wallet that treats its own AI agent as untrusted and uses key isolation, hard policies, and Passkey to protect on-chain assets.

~$5.98M Lost: Aztec, Raydium & More | BlockSec Weekly
Security Insights

~$5.98M Lost: Aztec, Raydium & More | BlockSec Weekly

This weekly blockchain security report covers the period of June 8 to June 14, 2026, analyzing 4 notable incidents across Ethereum and Solana with total losses of approximately $5.98M. The highlighted events include Aztec Connect, where a missing input validation allowed the rollup's proof path and L1 settlement to reach inconsistent states, and Raydium, where a missing validation check on the legacy AMM v3 program allowed an attacker to manipulate the LP token redemption calculation and drain four pools. Both vulnerabilities had been live for years before exploitation. The report examines attack types including lack of input validation, integer overflow, and governance capture.

Zcash Orchard Soundness Bug Analysis | BlockSec Weekly
Security Insights

Zcash Orchard Soundness Bug Analysis | BlockSec Weekly

During the week of June 1, 2026, a critical soundness vulnerability was publicly disclosed in Zcash's Orchard shielded pool circuit, caused by a missing equality constraint in the halo2 ECC scalar multiplication gadget that could have enabled undetectable counterfeiting of ZEC within the Orchard pool through double-spending. The vulnerability, which existed for over four years since Orchard's activation in May 2022, was discovered by an AI-assisted security audit and patched through an emergency network upgrade (NU6.2). This single-event report covers the technical root cause (under-constrained ZK circuit relation), the AI-assisted discovery by researcher Taylor Hornby using Anthropic's Opus 4.8 model, the emergency response timeline, and the broader implications for the ZKP ecosystem.

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit