-
Notifications
You must be signed in to change notification settings - Fork 1
/
PricePoints.sol
355 lines (289 loc) · 14.5 KB
/
PricePoints.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: MIT
pragma solidity ^0.8.20;
import {Math} from "./Math.sol";
import {IPricePoints} from "../interfaces/IPricePoints.sol";
/**
* @title Price Points Abstract Contract
* @dev Abstract contract that provides helper functions to calculate prices and amounts.
* This contract is used to calculate how much of one token can be bought/sold for another token following
* a price curve defined by price points.
* The curve is defined by a series of price points. Between each price point, the price is linearly interpolated.
* The price points are defined by the price of the base token in the quote token in 1e18, without decimals.
* For example, if the price of 1 base token (12 decimals) is 1 quote tokens (6 decimals), the price point is 1e18.
* Each segment of the curve will contain `width = totalSupply / (pricePoints.length - 1)` base tokens.
* When the segment is fully bought/sold, the prices will move to the next/previous price points.
*/
abstract contract PricePoints is IPricePoints {
/**
* @dev Returns the amount of quote tokens that should be sent (< 0) or received (> 0) for the specified base amount.
* @param supply The current supply of the base token.
* @param deltaBaseAmount The amount of base tokens to be sent (< 0) or received (> 0).
* @return actualDeltaBaseAmount The actual amount of base tokens to be sent (< 0) or received (> 0).
* @return deltaQuoteAmount The amount of quote tokens to be sent (< 0) or received (> 0).
*/
function getDeltaQuoteAmount(uint256 supply, int256 deltaBaseAmount)
public
view
override
returns (int256 actualDeltaBaseAmount, int256 deltaQuoteAmount)
{
if (deltaBaseAmount == 0) return (0, 0);
bool exactOut = deltaBaseAmount < 0;
(uint256 circSupply, uint256 base) = exactOut
? (supply, uint256(-deltaBaseAmount))
: (supply - uint256(deltaBaseAmount), uint256(deltaBaseAmount));
(uint256 baseAmount, uint256 quoteAmount) = _getQuoteAmount(circSupply, base, exactOut, exactOut);
if ((baseAmount | quoteAmount) > uint256(type(int256).max)) revert PricePoints__OverflowInt256();
return deltaBaseAmount > 0
? (int256(baseAmount), -int256(quoteAmount))
: (-int256(baseAmount), int256(quoteAmount));
}
/**
* @dev Returns the amount of base tokens that should be sent (< 0) or received (> 0) for the specified quote amount.
* @param supply The current supply of the base token.
* @param deltaQuoteAmount The amount of quote tokens to be sent (< 0) or received (> 0).
* @return deltaBaseAmount The amount of base tokens to be sent (< 0) or received (> 0).
* @return actualDeltaQuoteAmount The actual amount of quote tokens to be sent (< 0) or received (> 0).
*/
function getDeltaBaseAmount(uint256 supply, int256 deltaQuoteAmount)
public
view
override
returns (int256 deltaBaseAmount, int256 actualDeltaQuoteAmount)
{
if (deltaQuoteAmount == 0) return (0, 0);
uint256 baseAmount;
uint256 quoteAmount;
if (deltaQuoteAmount > 0) {
(baseAmount, quoteAmount) = _getBaseAmountOut(supply, uint256(deltaQuoteAmount));
(deltaBaseAmount, actualDeltaQuoteAmount) = (-int256(baseAmount), int256(quoteAmount));
} else {
(baseAmount, quoteAmount) = _getBaseAmountIn(supply, uint256(-deltaQuoteAmount));
(deltaBaseAmount, actualDeltaQuoteAmount) = (int256(baseAmount), -int256(quoteAmount));
}
if ((baseAmount | quoteAmount) > uint256(type(int256).max)) revert PricePoints__OverflowInt256();
}
/**
* @dev Returns the amount of base tokens and quote tokens that should be sent (exactOut) or received (!exactOut)
* @param supply The current supply of the base token.
* @param baseAmount The amount of base tokens to be sent (exactOut) or received (!exactOut).
* @param exactOut Whether the base amount is expected to be sent (true) or received (false).
* @param roundUp Whether to round up the quote amount.
* @return actualBaseAmount The actual amount of base tokens to be sent (exactOut) or received (!exactOut).
* @return quoteAmount The amount of quote tokens to be sent (exactOut) or received (!exactOut).
*/
function _getQuoteAmount(uint256 supply, uint256 baseAmount, bool exactOut, bool roundUp)
internal
view
returns (uint256 actualBaseAmount, uint256 quoteAmount)
{
if (supply > _totalSupply()) revert PricePoints__TotalSupplyExceeded();
uint256 length = _pricePointsLength();
uint256 basePrecision = _basePrecision();
uint256 widthScaled = _widthScaled();
uint256 scaledSupply = supply * 1e18 / basePrecision;
actualBaseAmount = baseAmount;
baseAmount = baseAmount * 1e18 / basePrecision;
uint256 i = scaledSupply / widthScaled;
scaledSupply = scaledSupply % widthScaled;
uint256 p0 = _pricePoints(i, exactOut);
while (baseAmount > 0 && ++i < length) {
uint256 p1 = _pricePoints(i, exactOut);
uint256 deltaBase = Math.min(baseAmount, widthScaled - scaledSupply);
uint256 deltaQuote = Math.mulDiv(
deltaBase,
(p1 - p0) * (deltaBase + 2 * scaledSupply) + 2 * p0 * widthScaled,
2e18 * widthScaled,
roundUp
);
quoteAmount += deltaQuote;
baseAmount -= deltaBase;
scaledSupply = 0;
p0 = p1;
}
return (
actualBaseAmount - Math.div((baseAmount) * basePrecision, 1e18, roundUp),
Math.div(quoteAmount * _quotePrecision(), 1e18, roundUp)
);
}
/**
* @dev Returns the amount of base tokens that should be sent and the quote amount that should be received for the
* specified quote amount.
* @param supply The current supply of the base token.
* @param quoteAmount The amount of quote tokens to be received.
* @return baseAmount The amount of base tokens to be sent.
* @return actualQuoteAmount The actual amount of quote tokens to be received.
*/
function _getBaseAmountOut(uint256 supply, uint256 quoteAmount)
internal
view
returns (uint256 baseAmount, uint256 actualQuoteAmount)
{
if (supply > _totalSupply()) revert PricePoints__TotalSupplyExceeded();
uint256 length = _pricePointsLength();
uint256 basePrecision = _basePrecision();
uint256 quotePrecision = _quotePrecision();
uint256 widthScaled = _widthScaled();
uint256 supplyScaled = supply * 1e18 / basePrecision;
uint256 remainingQuote = quoteAmount * 1e18 / quotePrecision;
uint256 i = supplyScaled / widthScaled;
uint256 base = supplyScaled % widthScaled;
uint256 p0 = _pricePoints(i, true);
while (remainingQuote > 0 && ++i < length) {
uint256 p1 = _pricePoints(i, true);
(uint256 deltaBase, uint256 deltaQuote) = _getDeltaBaseOut(p0, p1, widthScaled, base, remainingQuote);
baseAmount += deltaBase;
remainingQuote -= deltaQuote;
base = 0;
p0 = p1;
}
return (
Math.div(baseAmount * basePrecision, 1e18, false),
quoteAmount - Math.div(remainingQuote * quotePrecision, 1e18, false)
);
}
/**
* @dev Returns the amount of base tokens that should be received and the quote amount that should be sent for the
* specified quote amount.
* @param supply The current supply of the base token.
* @param quoteAmount The amount of quote tokens to be sent.
* @return baseAmount The amount of base tokens to be received.
* @return actualQuoteAmount The actual amount of quote tokens to be sent.
*/
function _getBaseAmountIn(uint256 supply, uint256 quoteAmount)
internal
view
returns (uint256 baseAmount, uint256 actualQuoteAmount)
{
if (supply > _totalSupply()) revert PricePoints__TotalSupplyExceeded();
uint256 basePrecision = _basePrecision();
uint256 quotePrecision = _quotePrecision();
uint256 widthScaled = _widthScaled();
uint256 supplyScaled = supply * 1e18 / basePrecision;
uint256 remainingQuote = quoteAmount * 1e18 / quotePrecision;
uint256 i = supplyScaled / widthScaled;
uint256 base = supplyScaled % widthScaled;
if (base == 0) base = widthScaled;
else ++i;
uint256 p1 = _pricePoints(i, false);
while (remainingQuote > 0 && i > 0) {
uint256 p0 = _pricePoints(--i, false);
(uint256 deltaBase, uint256 deltaQuote) = _getDeltaBaseIn(p0, p1, widthScaled, base, remainingQuote);
baseAmount += deltaBase;
remainingQuote -= deltaQuote;
base = widthScaled;
p1 = p0;
}
return (
Math.div(baseAmount * basePrecision, 1e18, true),
quoteAmount - Math.div(remainingQuote * quotePrecision, 1e18, true)
);
}
/**
* @dev Returns the delta base and quote amounts for the specified price points and remaining quote amount.
* @param p0 The price of the base token in the quote token at the current price point.
* @param p1 The price of the base token in the quote token at the next price point.
* @param widthScaled The width of the segment in scaled base tokens.
* @param base The amount of base tokens already purchased in the current segment.
* @param remainingQuote The remaining quote tokens.
* @return deltaBase The amount of base tokens to be sent (< 0) or received (> 0).
* @return deltaQuote The amount of quote tokens to be sent (< 0) or received (> 0).
*/
function _getDeltaBaseOut(uint256 p0, uint256 p1, uint256 widthScaled, uint256 base, uint256 remainingQuote)
internal
pure
returns (uint256 deltaBase, uint256 deltaQuote)
{
uint256 dp = p1 - p0;
uint256 currentQuote = Math.mulDiv(base, dp * base + 2 * p0 * widthScaled, 2e18 * widthScaled, false);
uint256 nextQuote = Math.div((p0 + p1) * widthScaled, 2e18, true);
uint256 maxQuote = nextQuote - currentQuote;
if (remainingQuote >= maxQuote) {
deltaQuote = maxQuote;
deltaBase = widthScaled - base;
} else {
deltaQuote = remainingQuote;
uint256 sqrtDiscriminant = _getSqrtDiscriminant(dp, p0, widthScaled, currentQuote + deltaQuote, false);
uint256 rl = sqrtDiscriminant;
uint256 rr = p0 * widthScaled + base * dp;
deltaBase = Math.div(rl - rr, dp, false);
}
}
/**
* @dev Returns the delta base and quote amounts for the specified price points and remaining quote amount.
* @param p0 The price of the base token in the quote token at the current price point.
* @param p1 The price of the base token in the quote token at the next price point.
* @param widthScaled The width of the segment in scaled base tokens.
* @param base The amount of base tokens already purchased in the current segment.
* @param remainingQuote The remaining quote tokens.
* @return deltaBase The amount of base tokens to be sent (< 0) or received (> 0).
* @return deltaQuote The amount of quote tokens to be sent (< 0) or received (> 0).
*/
function _getDeltaBaseIn(uint256 p0, uint256 p1, uint256 widthScaled, uint256 base, uint256 remainingQuote)
internal
pure
returns (uint256 deltaBase, uint256 deltaQuote)
{
uint256 dp = p1 - p0;
uint256 currentQuote = Math.mulDiv(base, dp * base + 2 * p0 * widthScaled, 2e18 * widthScaled, false);
if (remainingQuote >= currentQuote) {
deltaQuote = currentQuote;
deltaBase = base;
} else {
deltaQuote = remainingQuote;
uint256 sqrtDiscriminant = _getSqrtDiscriminant(dp, p0, widthScaled, currentQuote - deltaQuote, false);
uint256 rl = p0 * widthScaled + base * dp;
uint256 rr = sqrtDiscriminant;
deltaBase = Math.div(rl - rr, dp, true);
}
}
/**
* @dev Returns the square root of the discriminant for the specified price points and remaining quote amount.
* @param dp The difference between the price of the base token in the quote token at the next price point and the
* current price point.
* @param p0 The price of the base token in the quote token at the current price point.
* @param widthScaled The width of the segment in scaled base tokens.
* @param currentQuote The current quote amount.
* @param roundUp Whether to round up the result.
* @return sqrtDiscriminant The square root of the discriminant.
*/
function _getSqrtDiscriminant(uint256 dp, uint256 p0, uint256 widthScaled, uint256 currentQuote, bool roundUp)
internal
pure
returns (uint256 sqrtDiscriminant)
{
(uint256 dl0, uint256 dl1) = Math.mul512(widthScaled * dp, currentQuote * 2e18);
(uint256 dr0, uint256 dr1) = Math.mul512(p0 * widthScaled, p0 * widthScaled);
(uint256 d0, uint256 d1) = Math.add512(dl0, dl1, dr0, dr1);
return Math.sqrt512(d0, d1, roundUp);
}
/**
* @dev Returns the total supply of the base token.
*/
function _totalSupply() internal view virtual returns (uint256);
/**
* @dev Returns the width of the segment in scaled base tokens.
*/
function _widthScaled() internal view virtual returns (uint256);
/**
* @dev Returns the precision of the base token.
*/
function _basePrecision() internal view virtual returns (uint256);
/**
* @dev Returns the precision of the quote token.
*/
function _quotePrecision() internal view virtual returns (uint256);
/**
* @dev Returns the number of price points.
*/
function _pricePointsLength() internal view virtual returns (uint256);
/**
* @dev Returns the price points using the immutable arguments.
* This function doesn't check that the index is within bounds. It should be done by the parent function.
* @param i The index of the price point.
* @param askPrice Whether to get the ask price (true), ie, the price at which the user can sell the base token,
* or the bid price (false), ie, the price at which the user can buy the base token.
* @return The price of the base token in the quote token at the specified index.
*/
function _pricePoints(uint256 i, bool askPrice) internal view virtual returns (uint256);
}