This repository has been archived by the owner on Nov 19, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMarket.sol
293 lines (258 loc) · 11.2 KB
/
Market.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
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity =0.7.6;
pragma abicoder v2;
import {
MarketStorage,
MarketParameters,
CashGroupParameters
} from "../../global/Types.sol";
import {LibStorage} from "../../global/LibStorage.sol";
import {Constants} from "../../global/Constants.sol";
import {SafeInt256} from "../../math/SafeInt256.sol";
import {SafeUint256} from "../../math/SafeUint256.sol";
import {Emitter} from "../Emitter.sol";
import {BalanceHandler} from "../balances/BalanceHandler.sol";
import {DateTime} from "./DateTime.sol";
import {InterestRateCurve} from "./InterestRateCurve.sol";
library Market {
using SafeUint256 for uint256;
using SafeInt256 for int256;
/// @notice Add liquidity to a market, assuming that it is initialized. If not then
/// this method will revert and the market must be initialized first.
/// Return liquidityTokens and negative fCash to the portfolio
function addLiquidity(MarketParameters memory market, int256 primeCash)
internal
returns (int256 liquidityTokens, int256 fCash)
{
require(market.totalLiquidity > 0, "M: zero liquidity");
if (primeCash == 0) return (0, 0);
require(primeCash > 0); // dev: negative asset cash
liquidityTokens = market.totalLiquidity.mul(primeCash).div(market.totalPrimeCash);
// No need to convert this to underlying, primeCash / totalPrimeCash is a unitless proportion.
fCash = market.totalfCash.mul(primeCash).div(market.totalPrimeCash);
market.totalLiquidity = market.totalLiquidity.add(liquidityTokens);
market.totalfCash = market.totalfCash.add(fCash);
market.totalPrimeCash = market.totalPrimeCash.add(primeCash);
_setMarketStorageForLiquidity(market);
// Flip the sign to represent the LP's net position
fCash = fCash.neg();
}
/// @notice Remove liquidity from a market, assuming that it is initialized.
/// Return primeCash and positive fCash to the portfolio
function removeLiquidity(MarketParameters memory market, int256 tokensToRemove)
internal
returns (int256 primeCash, int256 fCash)
{
if (tokensToRemove == 0) return (0, 0);
require(tokensToRemove > 0); // dev: negative tokens to remove
primeCash = market.totalPrimeCash.mul(tokensToRemove).div(market.totalLiquidity);
fCash = market.totalfCash.mul(tokensToRemove).div(market.totalLiquidity);
market.totalLiquidity = market.totalLiquidity.subNoNeg(tokensToRemove);
market.totalfCash = market.totalfCash.subNoNeg(fCash);
market.totalPrimeCash = market.totalPrimeCash.subNoNeg(primeCash);
_setMarketStorageForLiquidity(market);
}
function executeTrade(
MarketParameters memory market,
address account,
CashGroupParameters memory cashGroup,
int256 fCashToAccount,
uint256 timeToMaturity,
uint256 marketIndex
) internal returns (int256 netPrimeCash) {
int256 netPrimeCashToReserve;
(netPrimeCash, netPrimeCashToReserve) = InterestRateCurve.calculatefCashTrade(
market,
cashGroup,
fCashToAccount,
timeToMaturity,
marketIndex
);
MarketStorage storage marketStorage = _getMarketStoragePointer(market);
_setMarketStorage(
marketStorage,
market.totalfCash,
market.totalPrimeCash,
market.lastImpliedRate,
market.oracleRate,
market.previousTradeTime
);
BalanceHandler.incrementFeeToReserve(cashGroup.currencyId, netPrimeCashToReserve);
Emitter.emitfCashMarketTrade(
account, cashGroup.currencyId, market.maturity, fCashToAccount, netPrimeCash, netPrimeCashToReserve
);
}
function getOracleRate(
uint256 currencyId,
uint256 maturity,
uint256 rateOracleTimeWindow,
uint256 blockTime
) internal view returns (uint256) {
mapping(uint256 => mapping(uint256 =>
mapping(uint256 => MarketStorage))) storage store = LibStorage.getMarketStorage();
uint256 settlementDate = DateTime.getReferenceTime(blockTime) + Constants.QUARTER;
MarketStorage storage marketStorage = store[currencyId][maturity][settlementDate];
uint256 lastImpliedRate = marketStorage.lastImpliedRate;
uint256 oracleRate = marketStorage.oracleRate;
uint256 previousTradeTime = marketStorage.previousTradeTime;
// If the oracle rate is set to zero this can only be because the markets have past their settlement
// date but the new set of markets has not yet been initialized. This means that accounts cannot be liquidated
// during this time, but market initialization can be called by anyone so the actual time that this condition
// exists for should be quite short.
require(oracleRate > 0, "Market not initialized");
return
InterestRateCurve.updateRateOracle(
previousTradeTime,
lastImpliedRate,
oracleRate,
rateOracleTimeWindow,
blockTime
);
}
/// @notice Reads a market object directly from storage. `loadMarket` should be called instead of this method
/// which ensures that the rate oracle is set properly.
function _loadMarketStorage(
MarketParameters memory market,
uint256 currencyId,
uint256 maturity,
bool needsLiquidity,
uint256 settlementDate
) private view {
// Market object always uses the most current reference time as the settlement date
mapping(uint256 => mapping(uint256 =>
mapping(uint256 => MarketStorage))) storage store = LibStorage.getMarketStorage();
MarketStorage storage marketStorage = store[currencyId][maturity][settlementDate];
bytes32 slot;
assembly {
slot := marketStorage.slot
}
market.storageSlot = slot;
market.maturity = maturity;
market.totalfCash = marketStorage.totalfCash;
market.totalPrimeCash = marketStorage.totalPrimeCash;
market.lastImpliedRate = marketStorage.lastImpliedRate;
market.oracleRate = marketStorage.oracleRate;
market.previousTradeTime = marketStorage.previousTradeTime;
if (needsLiquidity) {
market.totalLiquidity = marketStorage.totalLiquidity;
} else {
market.totalLiquidity = 0;
}
}
function _getMarketStoragePointer(
MarketParameters memory market
) private pure returns (MarketStorage storage marketStorage) {
bytes32 slot = market.storageSlot;
assembly {
marketStorage.slot := slot
}
}
function _setMarketStorageForLiquidity(MarketParameters memory market) internal {
MarketStorage storage marketStorage = _getMarketStoragePointer(market);
// Oracle rate does not change on liquidity
uint32 storedOracleRate = marketStorage.oracleRate;
_setMarketStorage(
marketStorage,
market.totalfCash,
market.totalPrimeCash,
market.lastImpliedRate,
storedOracleRate,
market.previousTradeTime
);
_setTotalLiquidity(marketStorage, market.totalLiquidity);
}
function setMarketStorageForInitialize(
MarketParameters memory market,
uint256 currencyId,
uint256 settlementDate
) internal {
// On initialization we have not yet calculated the storage slot so we get it here.
mapping(uint256 => mapping(uint256 =>
mapping(uint256 => MarketStorage))) storage store = LibStorage.getMarketStorage();
MarketStorage storage marketStorage = store[currencyId][market.maturity][settlementDate];
_setMarketStorage(
marketStorage,
market.totalfCash,
market.totalPrimeCash,
market.lastImpliedRate,
market.oracleRate,
market.previousTradeTime
);
_setTotalLiquidity(marketStorage, market.totalLiquidity);
}
function _setTotalLiquidity(
MarketStorage storage marketStorage,
int256 totalLiquidity
) internal {
require(totalLiquidity >= 0 && totalLiquidity <= type(uint80).max); // dev: market storage totalLiquidity overflow
marketStorage.totalLiquidity = uint80(totalLiquidity);
}
function _setMarketStorage(
MarketStorage storage marketStorage,
int256 totalfCash,
int256 totalPrimeCash,
uint256 lastImpliedRate,
uint256 oracleRate,
uint256 previousTradeTime
) private {
require(totalfCash >= 0 && totalfCash <= type(uint80).max); // dev: storage totalfCash overflow
require(totalPrimeCash >= 0 && totalPrimeCash <= type(uint80).max); // dev: storage totalPrimeCash overflow
require(0 < lastImpliedRate && lastImpliedRate <= type(uint32).max); // dev: storage lastImpliedRate overflow
require(0 < oracleRate && oracleRate <= type(uint32).max); // dev: storage oracleRate overflow
require(0 <= previousTradeTime && previousTradeTime <= type(uint32).max); // dev: storage previous trade time overflow
marketStorage.totalfCash = uint80(totalfCash);
marketStorage.totalPrimeCash = uint80(totalPrimeCash);
marketStorage.lastImpliedRate = uint32(lastImpliedRate);
marketStorage.oracleRate = uint32(oracleRate);
marketStorage.previousTradeTime = uint32(previousTradeTime);
}
/// @notice Creates a market object and ensures that the rate oracle time window is updated appropriately.
function loadMarket(
MarketParameters memory market,
uint256 currencyId,
uint256 maturity,
uint256 blockTime,
bool needsLiquidity,
uint256 rateOracleTimeWindow
) internal view {
// Always reference the current settlement date
uint256 settlementDate = DateTime.getReferenceTime(blockTime) + Constants.QUARTER;
loadMarketWithSettlementDate(
market,
currencyId,
maturity,
blockTime,
needsLiquidity,
rateOracleTimeWindow,
settlementDate
);
}
/// @notice Creates a market object and ensures that the rate oracle time window is updated appropriately, this
/// is mainly used in the InitializeMarketAction contract.
function loadMarketWithSettlementDate(
MarketParameters memory market,
uint256 currencyId,
uint256 maturity,
uint256 blockTime,
bool needsLiquidity,
uint256 rateOracleTimeWindow,
uint256 settlementDate
) internal view {
_loadMarketStorage(market, currencyId, maturity, needsLiquidity, settlementDate);
market.oracleRate = InterestRateCurve.updateRateOracle(
market.previousTradeTime,
market.lastImpliedRate,
market.oracleRate,
rateOracleTimeWindow,
blockTime
);
}
function loadSettlementMarket(
MarketParameters memory market,
uint256 currencyId,
uint256 maturity,
uint256 settlementDate
) internal view {
_loadMarketStorage(market, currencyId, maturity, true, settlementDate);
}
}