-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathGmxHelper.sol
More file actions
367 lines (325 loc) · 14.3 KB
/
GmxHelper.sol
File metadata and controls
367 lines (325 loc) · 14.3 KB
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {console} from "forge-std/Test.sol";
import {IERC20} from "../../interfaces/IERC20.sol";
import {IExchangeRouter} from "../../interfaces/IExchangeRouter.sol";
import {IDataStore} from "../../interfaces/IDataStore.sol";
import {IReader} from "../../interfaces/IReader.sol";
import {Keys} from "../../lib/Keys.sol";
import {Math} from "../../lib/Math.sol";
import {Order} from "../../types/Order.sol";
import {Position} from "../../types/Position.sol";
import {MarketUtils} from "../../types/MarketUtils.sol";
import {Price} from "../../types/Price.sol";
import {ReaderPositionUtils} from "../../types/ReaderPositionUtils.sol";
import {IBaseOrderUtils} from "../../types/IBaseOrderUtils.sol";
import {Oracle} from "../../lib/Oracle.sol";
import "../../Constants.sol";
abstract contract GmxHelper {
IDataStore constant dataStore = IDataStore(DATA_STORE);
IExchangeRouter constant exchangeRouter = IExchangeRouter(EXCHANGE_ROUTER);
IReader constant reader = IReader(READER);
// Note: both long and short token price must return 8 decimals (1e8 = 1 USD)
uint256 private constant CHAINLINK_MULTIPLIER = 1e8;
uint256 private constant CHAINLINK_DECIMALS = 8;
IERC20 public immutable marketToken;
IERC20 public immutable longToken;
IERC20 public immutable shortToken;
uint256 public immutable longTokenDecimals;
uint256 public immutable shortTokenDecimals;
address public immutable chainlinkLongToken;
address public immutable chainlinkShortToken;
Oracle immutable oracle;
constructor(
address _marketToken,
address _longToken,
address _shortToken,
address _chainlinkLongToken,
address _chainlinkShortToken,
address _oracle
) {
marketToken = IERC20(_marketToken);
longToken = IERC20(_longToken);
shortToken = IERC20(_shortToken);
longTokenDecimals = uint256(longToken.decimals());
shortTokenDecimals = uint256(shortToken.decimals());
require(
longTokenDecimals + CHAINLINK_DECIMALS <= 30,
"long + chainlink decimals > 30"
);
require(
shortTokenDecimals + CHAINLINK_DECIMALS <= 30,
"short + chainlink decimals > 30"
);
chainlinkLongToken = _chainlinkLongToken;
chainlinkShortToken = _chainlinkShortToken;
oracle = Oracle(_oracle);
}
function getPositionKey() internal view returns (bytes32 positionKey) {
return Position.getPositionKey({
account: address(this),
market: address(marketToken),
collateralToken: address(longToken),
isLong: false
});
}
function getPosition(bytes32 positionKey)
internal
view
returns (Position.Props memory)
{
return reader.getPosition(address(dataStore), positionKey);
}
// Returns collateral amount locked in the current position
function getPositionCollateralAmount() internal view returns (uint256) {
bytes32 positionKey = getPositionKey();
Position.Props memory position = getPosition(positionKey);
return position.numbers.collateralAmount;
}
// Returns the max callback gas limit used for calling a callback contract
// once a order is executed.
function getMaxCallbackGasLimit() internal view returns (uint256) {
return dataStore.getUint(Keys.MAX_CALLBACK_GAS_LIMIT);
}
// Returns position collateral amount + profit and loss of the position in terms of the collateral token
function getPositionWithPnlInToken() internal view returns (int256) {
bytes32 positionKey = getPositionKey();
Position.Props memory position = getPosition(positionKey);
if (
position.numbers.sizeInUsd == 0
|| position.numbers.collateralAmount == 0
) {
return 0;
}
uint256 longTokenPrice = oracle.getPrice(chainlinkLongToken);
uint256 shortTokenPrice = oracle.getPrice(chainlinkShortToken);
// +/- 0.1% of current prices of the long token
uint256 minLongTokenPrice = longTokenPrice
* 10 ** (30 - CHAINLINK_DECIMALS - longTokenDecimals) * 999 / 1000;
uint256 maxLongTokenPrice = longTokenPrice
* 10 ** (30 - CHAINLINK_DECIMALS - longTokenDecimals) * 1001 / 1000;
require(minLongTokenPrice > 0, "min long token price = 0");
require(maxLongTokenPrice > 0, "max long token price = 0");
MarketUtils.MarketPrices memory prices = MarketUtils.MarketPrices({
indexTokenPrice: Price.Props({
min: minLongTokenPrice,
max: maxLongTokenPrice
}),
longTokenPrice: Price.Props({
min: minLongTokenPrice,
max: maxLongTokenPrice
}),
shortTokenPrice: Price.Props({
min: shortTokenPrice
* 10 ** (30 - CHAINLINK_DECIMALS - shortTokenDecimals) * 999 / 1000,
max: shortTokenPrice
* 10 ** (30 - CHAINLINK_DECIMALS - shortTokenDecimals) * 1001 / 1000
})
});
ReaderPositionUtils.PositionInfo memory info = reader.getPositionInfo({
dataStore: address(dataStore),
referralStorage: REFERRAL_STORAGE,
positionKey: positionKey,
prices: prices,
// Use current position size for size delta
sizeDeltaUsd: 0,
uiFeeReceiver: address(0),
usePositionSizeAsSizeDeltaUsd: true
});
int256 collateralUsd =
Math.toInt256(position.numbers.collateralAmount * minLongTokenPrice);
int256 collateralCostUsd =
Math.toInt256(info.fees.totalCostAmount * minLongTokenPrice);
int256 remainingCollateralUsd =
collateralUsd + info.pnlAfterPriceImpactUsd - collateralCostUsd;
int256 remainingCollateral =
remainingCollateralUsd / Math.toInt256(minLongTokenPrice);
return remainingCollateral;
}
// Task 1: Calculate position size delta
function getSizeDeltaUsd(
// Long token price from Chainlink (1e8 = 1 USD)
uint256 longTokenPrice,
// Current position size
uint256 sizeInUsd,
// Current collateral amount locked in the position
uint256 collateralAmount,
// Long token amount to add or remove
uint256 longTokenAmount,
// True for market increase
bool isIncrease
) internal view returns (uint256 sizeDeltaUsd) {
// Calculate sizeDeltaUsd so that new position's leverage is close to 1
if (isIncrease) {
// new position size = long token price * new collateral amount
// new collateral amount = position.collateralAmount + longTokenAmount
// sizeDeltaUsd = new position size - position.sizeInUsd
uint256 newCollateralAmount = collateralAmount + longTokenAmount;
uint256 newPositionSizeInUsd = newCollateralAmount * longTokenPrice
* 10 ** (30 - longTokenDecimals - CHAINLINK_DECIMALS);
if (newPositionSizeInUsd > sizeInUsd) {
sizeDeltaUsd = newPositionSizeInUsd - sizeInUsd;
}
} else {
// new position size = long token price * new collateral amount
// new collateral amount = position.collateralAmount - longTokenAmount
// sizeDeltaUsd = new position size - position.sizeInUsd
uint256 newCollateralAmount = collateralAmount - longTokenAmount;
uint256 newPositionSizeInUsd = newCollateralAmount * longTokenPrice
* 10 ** (30 - longTokenDecimals - CHAINLINK_DECIMALS);
if (sizeInUsd > newPositionSizeInUsd) {
sizeDeltaUsd = sizeInUsd - newPositionSizeInUsd;
}
}
}
// Task 2: Create market increase order
function createIncreaseShortPositionOrder(
// Execution fee to send to the order vault
uint256 executionFee,
// Long token amount to add to the current position
uint256 longTokenAmount
) internal returns (bytes32 orderKey) {
uint256 longTokenPrice = oracle.getPrice(chainlinkLongToken);
bytes32 positionKey = getPositionKey();
Position.Props memory position = getPosition(positionKey);
// Task 2.1 - Calculate position size delta
uint256 sizeDeltaUsd = getSizeDeltaUsd({
longTokenPrice: longTokenPrice,
sizeInUsd: position.numbers.sizeInUsd,
collateralAmount: position.numbers.collateralAmount,
longTokenAmount: longTokenAmount,
isIncrease: true
});
// Task 2.2 - Create market increase order
// 90% of current long price
uint256 acceptablePrice =
longTokenPrice * 1e12 / CHAINLINK_MULTIPLIER * 90 / 100;
exchangeRouter.sendWnt{value: executionFee}({
receiver: ORDER_VAULT,
amount: executionFee
});
longToken.approve(ROUTER, longTokenAmount);
exchangeRouter.sendTokens({
token: address(longToken),
receiver: ORDER_VAULT,
amount: longTokenAmount
});
return exchangeRouter.createOrder(
IBaseOrderUtils.CreateOrderParams({
addresses: IBaseOrderUtils.CreateOrderParamsAddresses({
receiver: address(this),
cancellationReceiver: address(0),
callbackContract: address(0),
uiFeeReceiver: address(0),
market: address(marketToken),
initialCollateralToken: address(longToken),
swapPath: new address[](0)
}),
numbers: IBaseOrderUtils.CreateOrderParamsNumbers({
sizeDeltaUsd: sizeDeltaUsd,
// Set by amount of collateral sent to ORDER_VAULT
initialCollateralDeltaAmount: 0,
triggerPrice: 0,
acceptablePrice: acceptablePrice,
executionFee: executionFee,
callbackGasLimit: 0,
minOutputAmount: 0,
validFromTime: 0
}),
orderType: Order.OrderType.MarketIncrease,
decreasePositionSwapType: Order.DecreasePositionSwapType.NoSwap,
isLong: false,
shouldUnwrapNativeToken: false,
autoCancel: false,
referralCode: bytes32(uint256(0))
})
);
}
// Task 3: Create market decrease order
function createDecreaseShortPositionOrder(
// Execution fee to send to the order vault
uint256 executionFee,
// Long token amount to remove from the current position
uint256 longTokenAmount,
// Receiver of long token
address receiver,
// Callback contract used to handle withdrawal from the vault
address callbackContract,
// Max gas to send to the callback contract
uint256 callbackGasLimit
) internal returns (bytes32 orderKey) {
uint256 longTokenPrice = oracle.getPrice(chainlinkLongToken);
bytes32 positionKey = getPositionKey();
Position.Props memory position = getPosition(positionKey);
require(position.numbers.sizeInUsd > 0, "position size = 0");
longTokenAmount =
Math.min(longTokenAmount, position.numbers.collateralAmount);
require(longTokenAmount > 0, "long token amount = 0");
// Task 3.1 - Calculate position size delta
uint256 sizeDeltaUsd = getSizeDeltaUsd({
longTokenPrice: longTokenPrice,
sizeInUsd: position.numbers.sizeInUsd,
collateralAmount: position.numbers.collateralAmount,
longTokenAmount: longTokenAmount,
isIncrease: false
});
// Withdrawing collateral should also decrease position size
require(sizeDeltaUsd > 0, "size delta = 0");
// Task 3.2 - Send market decrease order
// 110% of current price
uint256 acceptablePrice =
longTokenPrice * 1e12 / CHAINLINK_MULTIPLIER * 110 / 100;
exchangeRouter.sendWnt{value: executionFee}({
receiver: ORDER_VAULT,
amount: executionFee
});
// Decreasing position that results in small position size causes liquidation error
return exchangeRouter.createOrder(
IBaseOrderUtils.CreateOrderParams({
addresses: IBaseOrderUtils.CreateOrderParamsAddresses({
receiver: receiver,
cancellationReceiver: address(0),
callbackContract: callbackContract,
uiFeeReceiver: address(0),
market: address(marketToken),
initialCollateralToken: address(longToken),
swapPath: new address[](0)
}),
numbers: IBaseOrderUtils.CreateOrderParamsNumbers({
sizeDeltaUsd: sizeDeltaUsd,
initialCollateralDeltaAmount: longTokenAmount,
triggerPrice: 0,
acceptablePrice: acceptablePrice,
executionFee: executionFee,
callbackGasLimit: callbackGasLimit,
minOutputAmount: 0,
validFromTime: 0
}),
orderType: Order.OrderType.MarketDecrease,
decreasePositionSwapType: Order
.DecreasePositionSwapType
.SwapPnlTokenToCollateralToken,
isLong: false,
shouldUnwrapNativeToken: false,
autoCancel: false,
referralCode: bytes32(uint256(0))
})
);
}
// Task 4: Cancel order
function cancelOrder(bytes32 orderKey) internal {
exchangeRouter.cancelOrder(orderKey);
}
// Task 5: Claim funding fees
function claimFundingFees() internal {
address[] memory markets = new address[](1);
markets[0] = address(marketToken);
address[] memory tokens = new address[](1);
tokens[0] = address(longToken);
exchangeRouter.claimFundingFees({
markets: markets,
tokens: tokens,
receiver: address(this)
});
}
}