-
Notifications
You must be signed in to change notification settings - Fork 232
/
amm.go
379 lines (312 loc) · 10.3 KB
/
amm.go
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
368
369
370
371
372
373
374
375
376
377
378
379
package types
import (
fmt "fmt"
"math/big"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/NibiruChain/nibiru/x/common"
"github.com/NibiruChain/nibiru/x/common/asset"
)
func (amm AMM) Validate() error {
if amm.BaseReserve.LTE(sdk.ZeroDec()) {
return fmt.Errorf("init pool token supply must be > 0")
}
if amm.QuoteReserve.LTE(sdk.ZeroDec()) {
return fmt.Errorf("init pool token supply must be > 0")
}
if amm.PriceMultiplier.LTE(sdk.ZeroDec()) {
return fmt.Errorf("init price multiplier must be > 0")
}
if amm.SqrtDepth.LTE(sdk.ZeroDec()) {
return fmt.Errorf("init sqrt depth must be > 0")
}
computedSqrtDepth, err := amm.ComputeSqrtDepth()
if err != nil {
return err
}
if !amm.SqrtDepth.Sub(computedSqrtDepth).Abs().LTE(sdk.OneDec()) {
return ErrLiquidityDepth.Wrap(
"computed sqrt and current sqrt are mismatched. pool: " + amm.String())
}
return nil
}
// returns the amount of quote reserve equivalent to the amount of quote asset given
func (amm AMM) FromQuoteAssetToReserve(quoteAsset sdk.Dec) sdk.Dec {
return quoteAsset.Quo(amm.PriceMultiplier)
}
// returns the amount of quote asset equivalent to the amount of quote reserve given
func (amm AMM) FromQuoteReserveToAsset(quoteReserve sdk.Dec) sdk.Dec {
return quoteReserve.Mul(amm.PriceMultiplier)
}
// Returns the amount of base reserve equivalent to the amount of quote reserve given
//
// args:
// - quoteReserveAmt: the amount of quote reserve before the trade, must be positive
// - dir: the direction of the trade
//
// returns:
// - baseReserveDelta: the amount of base reserve after the trade, unsigned
// - err: error
//
// NOTE: baseReserveDelta is always positive
// Throws an error if input quoteReserveAmt is negative, or if the final quote reserve is not positive
func (amm AMM) GetBaseReserveAmt(
quoteReserveAmt sdk.Dec, // unsigned
dir Direction,
) (baseReserveDelta sdk.Dec, err error) {
if quoteReserveAmt.IsNegative() {
return sdk.Dec{}, ErrInputQuoteAmtNegative
}
invariant := amm.QuoteReserve.Mul(amm.BaseReserve) // x * y = k
var quoteReservesAfter sdk.Dec
if dir == Direction_LONG {
quoteReservesAfter = amm.QuoteReserve.Add(quoteReserveAmt)
} else {
quoteReservesAfter = amm.QuoteReserve.Sub(quoteReserveAmt)
}
if !quoteReservesAfter.IsPositive() {
return sdk.Dec{}, ErrQuoteReserveAtZero
}
baseReservesAfter := invariant.Quo(quoteReservesAfter)
baseReserveDelta = baseReservesAfter.Sub(amm.BaseReserve).Abs()
return baseReserveDelta, nil
}
// returns the amount of quote reserve equivalent to the amount of base asset given
//
// args:
// - baseReserveAmt: the amount of base reserves to trade, must be positive
// - dir: the direction of the trade
//
// returns:
// - quoteReserveDelta: the amount of quote reserve after the trade
// - err: error
//
// NOTE: quoteReserveDelta is always positive
func (amm AMM) GetQuoteReserveAmt(
baseReserveAmt sdk.Dec,
dir Direction,
) (quoteReserveDelta sdk.Dec, err error) {
if baseReserveAmt.IsNegative() {
return sdk.Dec{}, ErrInputBaseAmtNegative
}
if baseReserveAmt.IsZero() {
return sdk.ZeroDec(), nil
}
invariant := amm.QuoteReserve.Mul(amm.BaseReserve) // x * y = k
var baseReservesAfter sdk.Dec
if dir == Direction_LONG {
baseReservesAfter = amm.BaseReserve.Sub(baseReserveAmt)
} else {
baseReservesAfter = amm.BaseReserve.Add(baseReserveAmt)
}
if !baseReservesAfter.IsPositive() {
return sdk.Dec{}, ErrBaseReserveAtZero.Wrapf(
"base assets below zero (%s) after trying to swap %s base assets",
baseReservesAfter.String(),
baseReserveAmt.String(),
)
}
quoteReservesAfter := invariant.Quo(baseReservesAfter)
quoteReserveDelta = quoteReservesAfter.Sub(amm.QuoteReserve).Abs()
return quoteReserveDelta, nil
}
// Returns the instantaneous mark price of the trading pair
func (amm AMM) MarkPrice() sdk.Dec {
if amm.BaseReserve.IsNil() || amm.BaseReserve.IsZero() ||
amm.QuoteReserve.IsNil() || amm.QuoteReserve.IsZero() {
return sdk.ZeroDec()
}
return amm.QuoteReserve.Quo(amm.BaseReserve).Mul(amm.PriceMultiplier)
}
// ComputeSqrtDepth: Returns the sqrt of the product of the reserves
func (amm AMM) ComputeSqrtDepth() (sqrtDepth sdk.Dec, err error) {
liqDepthBigInt := new(big.Int).Mul(amm.QuoteReserve.BigInt(), amm.BaseReserve.BigInt())
chopped := common.ChopPrecisionAndRound(liqDepthBigInt)
if chopped.BitLen() > common.MaxDecBitLen {
return sdk.Dec{}, ErrLiquidityDepthOverflow
}
// Since common.ChopPrecisionAndRound mutates the input, there's no guarantee that
// sdk.NewDecFromBigInt(liqDepthBigInt) is equal to amm.QuoteReserve.Mul(amm.BaseReserve)
liqDepth := amm.QuoteReserve.Mul(amm.BaseReserve)
return common.SqrtDec(liqDepth)
}
func (amm *AMM) WithPair(pair asset.Pair) *AMM {
amm.Pair = pair
return amm
}
func (amm *AMM) WithBaseReserve(baseReserve sdk.Dec) *AMM {
amm.BaseReserve = baseReserve
return amm
}
func (amm *AMM) WithQuoteReserve(quoteReserve sdk.Dec) *AMM {
amm.QuoteReserve = quoteReserve
return amm
}
func (amm *AMM) WithPriceMultiplier(priceMultiplier sdk.Dec) *AMM {
amm.PriceMultiplier = priceMultiplier
return amm
}
func (amm *AMM) WithTotalLong(totalLong sdk.Dec) *AMM {
amm.TotalLong = totalLong
return amm
}
func (amm *AMM) WithTotalShort(totalShort sdk.Dec) *AMM {
amm.TotalShort = totalShort
return amm
}
func (amm *AMM) WithSqrtDepth(sqrtDepth sdk.Dec) *AMM {
amm.SqrtDepth = sqrtDepth
return amm
}
// SwapQuoteAsset swaps base asset for quote asset
//
// args:
// - quoteAssetAmt: amount of base asset to swap, must be positive
// - dir: direction the user takes
//
// returns:
// - baseAssetDelta: amount of base asset received
// - err: error
//
// NOTE: baseAssetDelta is always positive
func (amm *AMM) SwapQuoteAsset(
quoteAssetAmt sdk.Dec, // unsigned
dir Direction,
) (baseAssetDelta sdk.Dec, err error) {
quoteReserveAmt := amm.FromQuoteAssetToReserve(quoteAssetAmt)
baseReserveDelta, err := amm.GetBaseReserveAmt(quoteReserveAmt, dir)
if err != nil {
return sdk.Dec{}, err
}
if dir == Direction_LONG {
amm.QuoteReserve = amm.QuoteReserve.Add(quoteReserveAmt)
amm.BaseReserve = amm.BaseReserve.Sub(baseReserveDelta)
amm.TotalLong = amm.TotalLong.Add(baseReserveDelta)
} else if dir == Direction_SHORT {
amm.QuoteReserve = amm.QuoteReserve.Sub(quoteReserveAmt)
amm.BaseReserve = amm.BaseReserve.Add(baseReserveDelta)
amm.TotalShort = amm.TotalShort.Add(baseReserveDelta)
}
return baseReserveDelta, nil
}
// SwapBaseAsset swaps base asset for quote asset
//
// args:
// - baseAssetAmt: amount of base asset to swap, must be positive
// - dir: direction of swap
//
// returns:
// - quoteAssetDelta: amount of quote asset received. Always positive
// - err: error if any
func (amm *AMM) SwapBaseAsset(baseAssetAmt sdk.Dec, dir Direction) (quoteAssetDelta sdk.Dec, err error) {
quoteReserveDelta, err := amm.GetQuoteReserveAmt(baseAssetAmt, dir)
if err != nil {
return sdk.Dec{}, err
}
if dir == Direction_LONG {
amm.QuoteReserve = amm.QuoteReserve.Add(quoteReserveDelta)
amm.BaseReserve = amm.BaseReserve.Sub(baseAssetAmt)
amm.TotalLong = amm.TotalLong.Add(baseAssetAmt)
} else if dir == Direction_SHORT {
amm.QuoteReserve = amm.QuoteReserve.Sub(quoteReserveDelta)
amm.BaseReserve = amm.BaseReserve.Add(baseAssetAmt)
amm.TotalShort = amm.TotalShort.Add(baseAssetAmt)
}
return amm.FromQuoteReserveToAsset(quoteReserveDelta), nil
}
/*
Bias returns the bias of the market in the base asset. It's the net amount of base assets for longs minus the net
amount of base assets for shorts.
*/
func (amm *AMM) Bias() (bias sdk.Dec) {
return amm.TotalLong.Sub(amm.TotalShort)
}
/*
CalcRepegCost provides the cost of re-pegging the pool to a new candidate peg multiplier.
*/
func (amm AMM) CalcRepegCost(newPriceMultiplier sdk.Dec) (cost sdkmath.Int, err error) {
if !newPriceMultiplier.IsPositive() {
return sdkmath.Int{}, ErrNonPositivePegMultiplier
}
bias := amm.Bias()
if bias.IsZero() {
return sdk.ZeroInt(), nil
}
var dir Direction
if bias.IsPositive() {
dir = Direction_SHORT
} else {
dir = Direction_LONG
}
biasInQuoteReserve, err := amm.GetQuoteReserveAmt(bias.Abs(), dir)
if err != nil {
return sdkmath.Int{}, err
}
costDec := biasInQuoteReserve.Mul(newPriceMultiplier.Sub(amm.PriceMultiplier))
if bias.IsNegative() {
costDec = costDec.Neg()
}
return costDec.Ceil().TruncateInt(), nil
}
// returns the amount of quote assets the amm has to pay out if all longs and shorts close out their positions
// positive value means the amm has to pay out quote assets
// negative value means the amm has to receive quote assets
func (amm AMM) GetMarketValue() (sdk.Dec, error) {
bias := amm.Bias()
if bias.IsZero() {
return sdk.ZeroDec(), nil
}
var dir Direction
if bias.IsPositive() {
dir = Direction_SHORT
} else {
dir = Direction_LONG
}
marketValueInReserves, err := amm.GetQuoteReserveAmt(bias.Abs(), dir)
if err != nil {
return sdk.Dec{}, err
}
if bias.IsNegative() {
marketValueInReserves = marketValueInReserves.Neg()
}
return amm.FromQuoteReserveToAsset(marketValueInReserves), nil
}
/*
CalcUpdateSwapInvariantCost returns the cost of updating the invariant of the pool
*/
func (amm AMM) CalcUpdateSwapInvariantCost(newSwapInvariant sdk.Dec) (sdkmath.Int, error) {
if newSwapInvariant.IsNil() {
return sdkmath.Int{}, ErrNilSwapInvariant
}
if !newSwapInvariant.IsPositive() {
return sdkmath.Int{}, ErrNegativeSwapInvariant
}
marketValueBefore, err := amm.GetMarketValue()
if err != nil {
return sdkmath.Int{}, err
}
err = amm.UpdateSwapInvariant(newSwapInvariant)
if err != nil {
return sdkmath.Int{}, err
}
marketValueAfter, err := amm.GetMarketValue()
if err != nil {
return sdkmath.Int{}, err
}
cost := marketValueAfter.Sub(marketValueBefore)
return cost.Ceil().TruncateInt(), nil
}
// UpdateSwapInvariant updates the swap invariant of the amm
func (amm *AMM) UpdateSwapInvariant(newSwapInvariant sdk.Dec) (err error) {
// k = x * y
// newK = (cx) * (cy) = c^2 xy = c^2 k
// newPrice = (c y) / (c x) = y / x = price | unchanged price
newSqrtDepth := common.MustSqrtDec(newSwapInvariant)
multiplier := newSqrtDepth.Quo(amm.SqrtDepth)
// Change the swap invariant while holding price constant.
// Multiplying by the same factor to both of the reserves won't affect price.
amm.SqrtDepth = newSqrtDepth
amm.BaseReserve = amm.BaseReserve.Mul(multiplier)
amm.QuoteReserve = amm.QuoteReserve.Mul(multiplier)
return amm.Validate() // might be useless
}