-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathClearingHouse.sol
355 lines (302 loc) · 12.9 KB
/
ClearingHouse.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
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
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.9;
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { HubbleBase } from "./legos/HubbleBase.sol";
import { IAMM, IInsuranceFund, IMarginAccount, IClearingHouse } from "./Interfaces.sol";
import { VUSD } from "./VUSD.sol";
contract ClearingHouse is IClearingHouse, HubbleBase {
using SafeCast for uint256;
using SafeCast for int256;
uint256 constant PRECISION = 1e6;
int256 override public maintenanceMargin;
uint override public tradeFee;
uint override public liquidationPenalty;
int256 public minAllowableMargin;
VUSD public vusd;
IInsuranceFund public insuranceFund;
IMarginAccount public marginAccount;
IAMM[] override public amms;
uint256[50] private __gap;
event PositionModified(address indexed trader, uint indexed idx, int256 baseAsset, uint quoteAsset, uint256 timestamp);
event PositionLiquidated(address indexed trader, uint indexed idx, int256 baseAsset, uint256 quoteAsset, uint256 timestamp);
event MarketAdded(uint indexed idx, address indexed amm);
constructor(address _trustedForwarder) HubbleBase(_trustedForwarder) {}
function initialize(
address _governance,
address _insuranceFund,
address _marginAccount,
address _vusd,
int256 _maintenanceMargin,
int256 _minAllowableMargin,
uint _tradeFee,
uint _liquidationPenalty
) external initializer {
_setGovernace(_governance);
insuranceFund = IInsuranceFund(_insuranceFund);
marginAccount = IMarginAccount(_marginAccount);
vusd = VUSD(_vusd);
require(_maintenanceMargin > 0, "_maintenanceMargin < 0");
maintenanceMargin = _maintenanceMargin;
minAllowableMargin = _minAllowableMargin;
tradeFee = _tradeFee;
liquidationPenalty = _liquidationPenalty;
}
/**
* @notice Open/Modify/Close Position
* @param idx AMM index
* @param baseAssetQuantity Quantity of the base asset to Long (baseAssetQuantity > 0) or Short (baseAssetQuantity < 0)
* @param quoteAssetLimit Rate at which the trade is executed in the AMM. Used to cap slippage.
*/
function openPosition(uint idx, int256 baseAssetQuantity, uint quoteAssetLimit) override external whenNotPaused {
_openPosition(_msgSender(), idx, baseAssetQuantity, quoteAssetLimit);
}
function closePosition(uint idx, uint quoteAssetLimit) override external whenNotPaused {
address trader = _msgSender();
(int256 size,,) = amms[idx].positions(trader);
_openPosition(trader, idx, -size, quoteAssetLimit);
}
function _openPosition(address trader, uint idx, int256 baseAssetQuantity, uint quoteAssetLimit) internal {
require(baseAssetQuantity != 0, "CH: baseAssetQuantity == 0");
updatePositions(trader); // adjust funding payments
(int realizedPnl, uint quoteAsset, bool isPositionIncreased) = amms[idx].openPosition(trader, baseAssetQuantity, quoteAssetLimit);
uint _tradeFee = _chargeFeeAndRealizePnL(trader, realizedPnl, quoteAsset, false /* isLiquidation */);
marginAccount.transferOutVusd(address(insuranceFund), _tradeFee);
if (isPositionIncreased) {
require(isAboveMinAllowableMargin(trader), "CH: Below Minimum Allowable Margin");
}
emit PositionModified(trader, idx, baseAssetQuantity, quoteAsset, _blockTimestamp());
}
/**
* @notice Add liquidity to the amm. The free margin from margin account is utilized for the same
* The liquidity can be provided on leverage.
* @param idx Index of the AMM
* @param baseAssetQuantity Amount of the asset to add to AMM. Equivalent amount of USD side is automatically added.
* This means that user is actually adding 2 * baseAssetQuantity * markPrice.
* @param minDToken Min amount of dTokens to receive. Used to cap slippage.
*/
function addLiquidity(uint idx, uint256 baseAssetQuantity, uint minDToken) override external whenNotPaused {
address maker = _msgSender();
updatePositions(maker);
amms[idx].addLiquidity(maker, baseAssetQuantity, minDToken);
require(isAboveMinAllowableMargin(maker), "CH: Below Minimum Allowable Margin");
}
/**
* @notice Remove liquidity from the amm.
* @param idx Index of the AMM
* @param dToken Measure of the liquidity to remove.
* @param minQuoteValue Min amount of USD to remove.
* @param minBaseValue Min amount of base to remove.
* Both the above params enable capping slippage in either direction.
*/
function removeLiquidity(uint idx, uint256 dToken, uint minQuoteValue, uint minBaseValue) override external whenNotPaused {
address maker = _msgSender();
updatePositions(maker);
(int256 realizedPnl,) = amms[idx].removeLiquidity(maker, dToken, minQuoteValue, minBaseValue);
marginAccount.realizePnL(maker, realizedPnl);
}
function updatePositions(address trader) override public whenNotPaused {
require(address(trader) != address(0), 'CH: 0x0 trader Address');
int256 fundingPayment;
for (uint i = 0; i < amms.length; i++) {
fundingPayment += amms[i].updatePosition(trader);
}
// -ve fundingPayment means trader should receive funds
marginAccount.realizePnL(trader, -fundingPayment);
}
function settleFunding() override external whenNotPaused {
for (uint i = 0; i < amms.length; i++) {
amms[i].settleFunding();
}
}
/* ****************** */
/* Liquidations */
/* ****************** */
function liquidate(address trader) override external whenNotPaused {
updatePositions(trader);
if (isMaker(trader)) {
_liquidateMaker(trader);
} else {
_liquidateTaker(trader);
}
}
function liquidateMaker(address maker) override public whenNotPaused {
updatePositions(maker);
_liquidateMaker(maker);
}
function liquidateTaker(address trader) override public whenNotPaused {
require(!isMaker(trader), 'CH: Remove Liquidity First');
updatePositions(trader);
_liquidateTaker(trader);
}
/* ********************* */
/* Liquidations Internal */
/* ********************* */
function _liquidateMaker(address maker) internal {
require(
_calcMarginFraction(maker, false) < maintenanceMargin,
"CH: Above Maintenance Margin"
);
int256 realizedPnl;
uint quoteAsset;
for (uint i = 0; i < amms.length; i++) {
(,, uint dToken,,,,) = amms[i].makers(maker);
// @todo put checks on slippage
(int256 _realizedPnl, uint _quote) = amms[i].removeLiquidity(maker, dToken, 0, 0);
realizedPnl += _realizedPnl;
quoteAsset += _quote;
}
_disperseLiquidationFee(
_chargeFeeAndRealizePnL(
maker,
realizedPnl,
2 * quoteAsset, // total liquidity value = 2 * quote value
true // isLiquidation
)
);
}
function _liquidateTaker(address trader) internal {
require(_calcMarginFraction(trader, false /* check funding payments again */) < maintenanceMargin, "Above Maintenance Margin");
int realizedPnl;
uint quoteAsset;
int256 size;
IAMM _amm;
for (uint i = 0; i < amms.length; i++) { // liquidate all positions
_amm = amms[i];
(size,,) = _amm.positions(trader);
if (size != 0) {
(int _realizedPnl, uint _quoteAsset) = _amm.liquidatePosition(trader);
realizedPnl += _realizedPnl;
quoteAsset += _quoteAsset;
emit PositionLiquidated(trader, i, size, _quoteAsset, _blockTimestamp());
}
}
_disperseLiquidationFee(
_chargeFeeAndRealizePnL(trader, realizedPnl, quoteAsset, true /* isLiquidation */)
);
}
function _disperseLiquidationFee(uint liquidationFee) internal {
if (liquidationFee > 0) {
uint toInsurance = liquidationFee / 2;
marginAccount.transferOutVusd(address(insuranceFund), toInsurance);
marginAccount.transferOutVusd(_msgSender(), liquidationFee - toInsurance);
}
}
function _chargeFeeAndRealizePnL(
address trader,
int realizedPnl,
uint quoteAsset,
bool isLiquidation
)
internal
returns (uint fee)
{
fee = isLiquidation ? _calculateLiquidationPenalty(quoteAsset) : _calculateTradeFee(quoteAsset);
int256 marginCharge = realizedPnl - fee.toInt256();
if (marginCharge != 0) {
marginAccount.realizePnL(trader, marginCharge);
}
}
/* ****************** */
/* View */
/* ****************** */
function isAboveMaintenanceMargin(address trader) override external view returns(bool) {
return getMarginFraction(trader) >= maintenanceMargin;
}
function isAboveMinAllowableMargin(address trader) override public view returns(bool) {
return getMarginFraction(trader) >= minAllowableMargin;
}
function getMarginFraction(address trader) override public view returns(int256) {
return _calcMarginFraction(trader, true /* includeFundingPayments */);
}
function isMaker(address trader) override public view returns(bool) {
for (uint i = 0; i < amms.length; i++) {
(,, uint dToken,,,,) = amms[i].makers(trader);
if (dToken > 0) {
return true;
}
}
return false;
}
function getTotalFunding(address trader) override public view returns(int256 totalFunding) {
int256 takerFundingPayment;
int256 makerFundingPayment;
for (uint i = 0; i < amms.length; i++) {
(takerFundingPayment, makerFundingPayment,,) = amms[i].getPendingFundingPayment(trader);
totalFunding += (takerFundingPayment + makerFundingPayment);
}
}
function getTotalNotionalPositionAndUnrealizedPnl(address trader)
override
public
view
returns(uint256 notionalPosition, int256 unrealizedPnl)
{
uint256 _notionalPosition;
int256 _unrealizedPnl;
for (uint i = 0; i < amms.length; i++) {
(_notionalPosition, _unrealizedPnl,,) = amms[i].getNotionalPositionAndUnrealizedPnl(trader);
notionalPosition += _notionalPosition;
unrealizedPnl += _unrealizedPnl;
}
}
function getNotionalPositionAndMargin(address trader, bool includeFundingPayments)
override
public
view
returns(uint256 notionalPosition, int256 margin)
{
int256 unrealizedPnl;
(notionalPosition, unrealizedPnl) = getTotalNotionalPositionAndUnrealizedPnl(trader);
margin = marginAccount.getNormalizedMargin(trader);
margin += unrealizedPnl;
if (includeFundingPayments) {
margin -= getTotalFunding(trader); // -ve fundingPayment means trader should receive funds
}
}
function getAmmsLength() override external view returns(uint) {
return amms.length;
}
function getAMMs() external view returns (IAMM[] memory) {
return amms;
}
/* ****************** */
/* Internal View */
/* ****************** */
function _calculateTradeFee(uint quoteAsset) internal view returns (uint) {
return quoteAsset * tradeFee / PRECISION;
}
function _calculateLiquidationPenalty(uint quoteAsset) internal view returns (uint) {
return quoteAsset * liquidationPenalty / PRECISION;
}
function _calcMarginFraction(address trader, bool includeFundingPayments) internal view returns(int256) {
(uint256 notionalPosition, int256 margin) = getNotionalPositionAndMargin(trader, includeFundingPayments);
return _getMarginFraction(margin, notionalPosition);
}
/* ****************** */
/* Pure */
/* ****************** */
function _getMarginFraction(int256 accountValue, uint notionalPosition) private pure returns(int256) {
if (notionalPosition == 0) {
return type(int256).max;
}
return accountValue * PRECISION.toInt256() / notionalPosition.toInt256();
}
/* ****************** */
/* Governance */
/* ****************** */
function whitelistAmm(address _amm) external onlyGovernance {
emit MarketAdded(amms.length, _amm);
amms.push(IAMM(_amm));
}
function setParams(
int _maintenanceMargin,
int _minAllowableMargin,
uint _tradeFee,
uint _liquidationPenality
) external onlyGovernance {
tradeFee = _tradeFee;
liquidationPenalty = _liquidationPenality;
maintenanceMargin = _maintenanceMargin;
minAllowableMargin = _minAllowableMargin;
}
}