Calculated from recorded token losses using historical USD prices at the incident time.
0x1751268e620767ff117c5c280e9214389b7c1961c42e77fc704fd88e22f4f77a0x41a2f9ab325577f92e8653853c12823b35fb35c4BSC0xccfe1a5b6e4ad16a4e41a9142673dec829f39402BSCThe seed transaction 0x1751268e620767ff117c5c280e9214389b7c1961c42e77fc704fd88e22f4f77a on BNB Smart Chain block 7785587 is a permissionless flash-swap exploit against JulProtocolV2 at 0x41a2f9ab325577f92e8653853c12823b35fb35c4. The attacker borrowed 70000 JULb, pushed down the JULb/WBNB spot price in the public BSCswap pool, then called addBNB() with 515 BNB while the manipulated reserves were live. That caused the protocol to contribute far too much treasury JULb into liquidity, after which the attacker bought JULb back more cheaply, repaid the flash borrow, and kept 522.838342910629238613 BNB profit.
The root cause is direct use of the same-transaction JULb/WBNB spot reserves as a pricing oracle for protocol treasury spending. JulProtocolV2.addBNB() quotes tokenAmount from current pool reserves and immediately spends protocol-owned JULb through addLiquidityBNB, with no TWAP, delay, or anti-manipulation control.
JulProtocolV2 maintains a treasury balance of JULb and uses that inventory to pair against user-supplied BNB in BSCswap liquidity positions. The relevant market is the public JULb/WBNB BSCswap pair 0xccfe1a5b6e4ad16a4e41a9142673dec829f39402, while the attacker sourced temporary JULb inventory from the permissionless JULb/old-JULD pair .
0x0242c5c11e3eaeb53298b45c7395dbadc8a120e7BSCswap follows Uniswap V2 flash-swap behavior: swap(..., to, data) transfers output before checking the invariant at the end of the call. That lets an unprivileged attacker borrow JULb, manipulate the spot price, interact with the victim, and repay inside one atomic transaction.
The vulnerability class is an attack based on flash-manipulable AMM spot pricing. JulProtocolV2 treats the live JULb/WBNB pool reserves as a trusted oracle for how much protocol-owned JULb to contribute when a user deposits BNB. Because those reserves can be manipulated inside the same transaction, the attacker can make the protocol overvalue BNB relative to JULb and overcontribute treasury JULb. The exploit therefore does not require any privileged role, leaked key, or off-chain secret.
The safety invariant is straightforward: a deposit of ethAmount BNB must not cause the protocol to spend more treasury JULb than the fair market rate from an unmanipulated state. The concrete breakpoint is inside addBNB(), where the contract reads current reserves and computes the treasury contribution from them:
function addBNB() public payable returns (uint256 amountToken, uint256 amountBNB, uint256 liquidity) {
uint ethAmount = msg.value;
(reserveA, reserveB) = BSCswapLibrary.getReserves(BSCSWAP_FACTORY, WBNB, TOKEN);
uint tokenAmount = BSCswapLibrary.quote(ethAmount, reserveA, reserveB);
uint256 balance = JulToken.balanceOf(address(this));
require(balance >= tokenAmount, "Insufficient JUL token amount");
JulToken.approve(router02Address, tokenAmount);
(amountToken, amountBNB, liquidity) = bscswapRouter02.addLiquidityBNB{value: ethAmount}(
TOKEN, tokenAmount, tokenAmount, 1, address(this), block.timestamp
);
}
This logic is enough to explain the exploit. Once the attacker depresses the JULb/WBNB spot price by dumping borrowed JULb into the pool, quote() returns an inflated tokenAmount, and the protocol spends treasury JULb at that distorted rate immediately.
The pre-state immediately before block 7785587 already satisfies all exploit prerequisites. JulProtocolV2 held free JULb treasury inventory, the JULb/WBNB pool was liquid, and the JULb/old-JULD pair held enough JULb to support a same-transaction flash borrow.
The trace shows the attacker contract receiving 70000000000000000000000 JULb from the flash pair and then querying reserves of the JULb/WBNB pair before the victim call. In the reproduced execution, the first reserve read returns roughly 15123.221631350493276974 JULb against 1703.553267273812456684 WBNB, and the attacker immediately transfers the entire borrowed JULb into that pair and receives 1400.146882180525770269 WBNB. That dump moves the pool to a severely distorted reserve ratio.
Next, the victim call occurs while the manipulated state is still active. The collector trace contains the direct 515000000000000000000 wei call into JulProtocolV2:
SM Address: 0x41a2f9ab325577f92e8653853c12823b35fb35c4, caller:0x7c591aab9429af81287951872595a17d5837ce03,target:0x41a2f9ab325577f92e8653853c12823b35fb35c4 is_static:false, transfer:Transfer(515000000000000000000), input_size:4
The later liquidity-add effects match the vulnerable code path. In the reproduced exploit trace, JulProtocolV2 contributes 144487595825205639607875 JULb together with 514999999999999999999 WBNB and receives 6781768441209196495678 LP tokens. Those values are the on-chain manifestation of the faulty reserve-based quote: the protocol spent treasury JULb according to a manipulated spot price.
After the protocol has donated treasury JULb into the pair, the attacker reverses the trade direction. The attacker sends back only 361566270666396330776 WBNB in the validator reproduction to withdraw 70210631895687061183551 JULb, then repays the flash pair with that amount. The flash pair ends with more JULb than it started with, proving repayment plus fee. The profit remains with the attacker contract.
The adversary cluster is identified directly from the seed transaction metadata. The EOA 0xc3bc29941677db01b9645f7b8b72d27e3ba75372 submitted the transaction and paid gas. The exploit executor was contract 0x7c591aab9429af81287951872595a17d5837ce03, which received the flash-borrowed JULb, called JulProtocolV2, and retained the profit.
The end-to-end flow is:
1. Flash borrow 70000 JULb from pair 0x0242c5c11e3eaeb53298b45c7395dbadc8a120e7.
2. Sell the borrowed JULb into JULb/WBNB pair 0xccfe1a5b6e4ad16a4e41a9142673dec829f39402.
3. Call JulProtocolV2.addBNB() with 515 BNB while the manipulated reserves are live.
4. Let JulProtocolV2 contribute treasury JULb into the pool at the distorted rate.
5. Buy JULb back from the now-rebalanced pool.
6. Repay the flash pair with fee.
7. Keep the remaining BNB profit.
The exploit is ACT because every step uses public, permissionless interfaces. The attacker needs only an unprivileged EOA, an attacker-controlled contract, and access to the public AMM pairs and JulProtocolV2.
The direct measurable loss is the attacker profit recorded in the collector balance diff:
{
"address": "0x7c591aab9429af81287951872595a17d5837ce03",
"before_wei": "0",
"after_wei": "522838342910629238613",
"delta_wei": "522838342910629238613"
}
The gas-paying EOA separately lost 2563130000000000 wei in fees, which does not change the protocol loss calculation. The value source of the exploit is JulProtocolV2 treasury JULb that the protocol overcommitted into the manipulated JULb/WBNB position.
0x1751268e620767ff117c5c280e9214389b7c1961c42e77fc704fd88e22f4f77aJulProtocolV2 at 0x41a2f9ab325577f92e8653853c12823b35fb35c40xccfe1a5b6e4ad16a4e41a9142673dec829f394020x0242c5c11e3eaeb53298b45c7395dbadc8a120e7addBNB()/workspace/session/artifacts/validator/forge-test.log