@sundaeswap/math 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/cjs/PoolMath/ConstantProductPool.js +4 -29
  2. package/dist/cjs/PoolMath/ConstantProductPool.js.map +1 -1
  3. package/dist/cjs/PoolMath/SharedPoolMath.js +33 -0
  4. package/dist/cjs/PoolMath/SharedPoolMath.js.map +1 -0
  5. package/dist/cjs/PoolMath/StableSwapsPool.js +414 -0
  6. package/dist/cjs/PoolMath/StableSwapsPool.js.map +1 -0
  7. package/dist/cjs/PoolMath/index.js +5 -1
  8. package/dist/cjs/PoolMath/index.js.map +1 -1
  9. package/dist/esm/PoolMath/ConstantProductPool.js +3 -26
  10. package/dist/esm/PoolMath/ConstantProductPool.js.map +1 -1
  11. package/dist/esm/PoolMath/SharedPoolMath.js +26 -0
  12. package/dist/esm/PoolMath/SharedPoolMath.js.map +1 -0
  13. package/dist/esm/PoolMath/StableSwapsPool.js +400 -0
  14. package/dist/esm/PoolMath/StableSwapsPool.js.map +1 -0
  15. package/dist/esm/PoolMath/index.js +2 -0
  16. package/dist/esm/PoolMath/index.js.map +1 -1
  17. package/dist/types/PoolMath/ConstantProductPool.d.ts +6 -36
  18. package/dist/types/PoolMath/ConstantProductPool.d.ts.map +1 -1
  19. package/dist/types/PoolMath/SharedPoolMath.d.ts +36 -0
  20. package/dist/types/PoolMath/SharedPoolMath.d.ts.map +1 -0
  21. package/dist/types/PoolMath/StableSwapsPool.d.ts +221 -0
  22. package/dist/types/PoolMath/StableSwapsPool.d.ts.map +1 -0
  23. package/dist/types/PoolMath/index.d.ts +2 -0
  24. package/dist/types/PoolMath/index.d.ts.map +1 -1
  25. package/dist/types/tsconfig.build.tsbuildinfo +1 -1
  26. package/package.json +1 -1
  27. package/src/PoolMath/ConstantProductPool.ts +10 -45
  28. package/src/PoolMath/SharedPoolMath.ts +41 -0
  29. package/src/PoolMath/StableSwapsPool.ts +527 -0
  30. package/src/PoolMath/__tests__/ConstantProductPool.test.ts +6 -4
  31. package/src/PoolMath/__tests__/StableSwapsPool.test.ts +518 -0
  32. package/src/PoolMath/index.ts +2 -0
@@ -0,0 +1,518 @@
1
+ import {
2
+ getNewY,
3
+ getPrice,
4
+ getSumInvariant,
5
+ getSwapInput,
6
+ getSwapOutput,
7
+ liquidityInvariant,
8
+ RESERVE_PRECISION,
9
+ } from "../StableSwapsPool.js";
10
+ import { TPair } from "../SharedPoolMath.js";
11
+ import { Fraction, type TFractionLike } from "@sundaeswap/fraction";
12
+
13
+ describe("liquidityInvariant()", () => {
14
+ test("Should be true for normal balanced case", () => {
15
+ expect(
16
+ liquidityInvariant(
17
+ 20_000_000n * RESERVE_PRECISION,
18
+ 20_000_000n * RESERVE_PRECISION,
19
+ 200n,
20
+ 40000000000000000000n,
21
+ ),
22
+ ).toBeTruthy();
23
+ });
24
+
25
+ const rawSwap = 9999750604846068206n;
26
+
27
+ test("Should be true after a swap", () => {
28
+ expect(
29
+ liquidityInvariant(
30
+ 1010000000n * RESERVE_PRECISION,
31
+ 1000000000n * RESERVE_PRECISION - rawSwap,
32
+ 200n,
33
+ 2000000000000000000000n,
34
+ ),
35
+ ).toBeTruthy();
36
+ });
37
+
38
+ test("Should be false after a swap minus 1", () => {
39
+ expect(
40
+ liquidityInvariant(
41
+ 1010000000n * RESERVE_PRECISION,
42
+ 1000000000n * RESERVE_PRECISION - rawSwap - 1n,
43
+ 200n,
44
+ 2000000000000000000000n,
45
+ ),
46
+ ).toBeFalsy();
47
+ });
48
+
49
+ test("Should be false after a swap plus 1", () => {
50
+ expect(
51
+ liquidityInvariant(
52
+ 1010000000n * RESERVE_PRECISION,
53
+ 1000000000n * RESERVE_PRECISION - rawSwap + 1n,
54
+ 200n,
55
+ 2000000000000000000000n,
56
+ ),
57
+ ).toBeFalsy();
58
+ });
59
+ });
60
+
61
+ describe("getSumInvariant()", () => {
62
+ test("Should return correct in balanced case", () => {
63
+ expect(getSumInvariant(200n, 20_000_000n, 20_000_000n)).toEqual(
64
+ 40000000000000000000n,
65
+ );
66
+ });
67
+ test("Should return correct in unbalanced case", () => {
68
+ expect(getSumInvariant(200n, 1010000000n, 1000000000n - 9999750n)).toEqual(
69
+ 2000000000604861229903n,
70
+ );
71
+ });
72
+ });
73
+
74
+ describe("getNewY()", () => {
75
+ const sumInvariant = 2000000000000000000000n;
76
+ const rawSwap = 9999750604846068206n;
77
+ const newX = 1010000000n;
78
+ const oldY = 1000000000n;
79
+ test("Should find the correct swap amount", () => {
80
+ const newY = getNewY(newX, 200n, sumInvariant);
81
+ expect(newY).toEqual(oldY * RESERVE_PRECISION - rawSwap);
82
+ });
83
+ });
84
+
85
+ describe("getPrice()", () => {
86
+ test("Should return a price of 1 for a balanced pool", () => {
87
+ const price = getPrice(20_000_000n, 20_000_000n, 200n);
88
+ expect(price.toNumber()).toEqual(1.0);
89
+ });
90
+
91
+ test("Should give a price closer to 1 than normal cpp for unbalanced pool", () => {
92
+ const aReserve = 1010000000n;
93
+ const bReserve = 1000000000n - 9999750n;
94
+ const stablePrice = getPrice(aReserve, bReserve, 200n);
95
+ const normalCppPrice = new Fraction(aReserve, bReserve);
96
+ expect(Math.abs(stablePrice.toNumber() - 1.0)).toBeLessThan(
97
+ Math.abs(normalCppPrice.toNumber() - 1.0),
98
+ );
99
+ });
100
+ });
101
+
102
+ describe("getSwapOutput()", () => {
103
+ const zeroThreePct = new Fraction(3, 1000);
104
+ const onePct = new Fraction(1, 100);
105
+ const zeroPct = Fraction.ZERO;
106
+ const laf = 1000n;
107
+
108
+ test("throws if any of the arguments are negative", () => {
109
+ expect(() =>
110
+ getSwapOutput(-1n, 10n, 10n, Fraction.ZERO, Fraction.ZERO, laf),
111
+ ).toThrow();
112
+ expect(() =>
113
+ getSwapOutput(1n, -10n, 10n, Fraction.ZERO, Fraction.ZERO, laf),
114
+ ).toThrow();
115
+ expect(() =>
116
+ getSwapOutput(1n, 10n, -10n, Fraction.ZERO, Fraction.ZERO, laf),
117
+ ).toThrow();
118
+ expect(() =>
119
+ getSwapOutput(1n, 1n, 1n, Fraction.asFraction(-0.1), Fraction.ZERO, laf),
120
+ ).toThrow();
121
+ expect(() =>
122
+ getSwapOutput(1n, 1n, 1n, Fraction.ZERO, Fraction.asFraction(-0.1), laf),
123
+ ).toThrow();
124
+ });
125
+
126
+ test("throws if combined fee is greater than 1", () => {
127
+ expect(() =>
128
+ getSwapOutput(
129
+ 1n,
130
+ 10000n,
131
+ 10000n,
132
+ Fraction.asFraction(0.5),
133
+ Fraction.asFraction(0.4),
134
+ laf,
135
+ ),
136
+ ).not.toThrow();
137
+ expect(() =>
138
+ getSwapOutput(
139
+ 1n,
140
+ 10000n,
141
+ 10000n,
142
+ Fraction.asFraction(0.632),
143
+ Fraction.asFraction(0.5),
144
+ laf,
145
+ ),
146
+ ).toThrow();
147
+ });
148
+
149
+ test("rounds up for non-fractional assets", () => {
150
+ expect(
151
+ getSwapOutput(83n, 10000n, 500n, zeroThreePct, zeroThreePct, laf).output,
152
+ ).toEqual(79n);
153
+ expect(
154
+ getSwapOutput(83n, 10000n, 500n, zeroThreePct, zeroThreePct, laf, true)
155
+ .output,
156
+ ).toEqual(80n);
157
+ });
158
+
159
+ test.each([
160
+ // Ideal, 0% pool
161
+ {
162
+ id: 1,
163
+ input: 1n,
164
+ inReserve: [2n, 2n],
165
+ fee: zeroPct,
166
+ out: 0n,
167
+ outReserve: [3n, 2n],
168
+ lpFee: 0n,
169
+ impact: new Fraction(0n, 0n),
170
+ },
171
+ {
172
+ id: 2,
173
+ input: 1n,
174
+ inReserve: [2n, 3n],
175
+ fee: zeroPct,
176
+ out: 1n,
177
+ outReserve: [3n, 2n],
178
+ lpFee: 0n,
179
+ impact: new Fraction(249n, 1000000n),
180
+ },
181
+ {
182
+ id: 3,
183
+ input: 100n,
184
+ inReserve: [2n, 3n],
185
+ fee: zeroPct,
186
+ out: 2n,
187
+ outReserve: [102n, 1n],
188
+ lpFee: 0n,
189
+ impact: new Fraction(245001n, 250000n),
190
+ },
191
+ {
192
+ id: 4,
193
+ input: 100n,
194
+ inReserve: [2n, 100n],
195
+ fee: zeroPct,
196
+ out: 99n,
197
+ outReserve: [102n, 1n],
198
+ lpFee: 0n,
199
+ impact: new Fraction(244453n, 1000000n),
200
+ },
201
+ {
202
+ id: 5,
203
+ input: 100n,
204
+ inReserve: [100n, 100n],
205
+ fee: zeroPct,
206
+ out: 98n,
207
+ outReserve: [200n, 2n],
208
+ lpFee: 0n,
209
+ impact: new Fraction(1n, 50n),
210
+ },
211
+ {
212
+ id: 6,
213
+ input: 50n,
214
+ inReserve: [100n, 100n],
215
+ fee: zeroPct,
216
+ out: 49n,
217
+ outReserve: [150n, 51n],
218
+ lpFee: 0n,
219
+ impact: new Fraction(1n, 50n),
220
+ },
221
+ // Simple boundary cases
222
+ {
223
+ id: 7,
224
+ input: 1n,
225
+ inReserve: [2n, 2n],
226
+ fee: zeroThreePct,
227
+ out: 0n,
228
+ outReserve: [3n, 2n],
229
+ lpFee: 0n,
230
+ impact: new Fraction(0n, 0n),
231
+ },
232
+ {
233
+ id: 8,
234
+ input: 1n,
235
+ inReserve: [2n, 3n],
236
+ fee: zeroThreePct,
237
+ out: 0n,
238
+ outReserve: [3n, 3n],
239
+ lpFee: 1n,
240
+ impact: new Fraction(249n, 1000000n),
241
+ },
242
+ {
243
+ id: 9,
244
+ input: 1n,
245
+ inReserve: [2n, 6n],
246
+ fee: zeroThreePct,
247
+ out: 0n,
248
+ outReserve: [3n, 6n],
249
+ lpFee: 1n,
250
+ impact: new Fraction(499n, 1000000n),
251
+ },
252
+ {
253
+ id: 10,
254
+ input: 100n,
255
+ inReserve: [2n, 3n],
256
+ fee: zeroThreePct,
257
+ out: 1n,
258
+ outReserve: [102n, 2n],
259
+ lpFee: 1n,
260
+ impact: new Fraction(495001n, 500000n),
261
+ },
262
+ {
263
+ id: 11,
264
+ input: 200n,
265
+ inReserve: [2n, 200n],
266
+ fee: zeroThreePct,
267
+ out: 196n,
268
+ outReserve: [202n, 3n],
269
+ lpFee: 1n,
270
+ impact: new Fraction(559n, 1000n),
271
+ },
272
+ {
273
+ id: 12,
274
+ input: 100n,
275
+ inReserve: [100n, 100n],
276
+ fee: zeroThreePct,
277
+ out: 97n,
278
+ outReserve: [200n, 3n],
279
+ lpFee: 1n,
280
+ impact: new Fraction(3n, 100n),
281
+ },
282
+ {
283
+ id: 13,
284
+ input: 50n,
285
+ inReserve: [100n, 100n],
286
+ fee: zeroThreePct,
287
+ out: 48n,
288
+ outReserve: [150n, 52n],
289
+ lpFee: 1n,
290
+ impact: new Fraction(1n, 25n),
291
+ },
292
+
293
+ // Real world examples
294
+ {
295
+ id: 14,
296
+ input: 1291591603n,
297
+ inReserve: [5753371381n, 672426600000n],
298
+ fee: onePct,
299
+ out: 2139306697n,
300
+ outReserve: [7044962984n, 670265463643n],
301
+ lpFee: 21829661n,
302
+ impact: new Fraction(380983n, 1000000n),
303
+ },
304
+ {
305
+ id: 15,
306
+ input: 1000000n,
307
+ inReserve: [3696260028076n, 77871393827281n],
308
+ fee: zeroThreePct,
309
+ out: 1024031n,
310
+ outReserve: [3696261028076n, 77871392800159n],
311
+ lpFee: 3091n,
312
+ impact: new Fraction(34239n, 1000000n),
313
+ },
314
+ ] as {
315
+ input: bigint;
316
+ inReserve: TPair;
317
+ fee: TFractionLike;
318
+ out: bigint;
319
+ outReserve: TPair;
320
+ lpFee: bigint;
321
+ impact: Fraction;
322
+ id: number;
323
+ }[])(
324
+ "gets the correct swap output, given different inputs (%#)",
325
+ ({ input, inReserve, fee, out, outReserve, lpFee, impact }) => {
326
+ const actual = getSwapOutput(input, ...inReserve, fee, fee, laf);
327
+ expect(actual.output).toBe(out);
328
+ expect(actual.outputLpFee).toBe(lpFee);
329
+ expect(actual.nextInputReserve).toBe(outReserve[0]);
330
+ expect(actual.nextInputReserve).toBe(input + inReserve[0]);
331
+ expect(actual.nextOutputReserve).toBe(outReserve[1]);
332
+ expect(
333
+ actual.nextOutputReserve + actual.output + actual.outputProtocolFee,
334
+ ).toBe(inReserve[1]);
335
+ expect(actual.priceImpact.toPrecision(6)).toStrictEqual(impact);
336
+ },
337
+ );
338
+ });
339
+
340
+ describe("getSwapInput", () => {
341
+ const fivePct = new Fraction(5, 100);
342
+ const threePct = new Fraction(3, 100);
343
+ const onePct = new Fraction(1, 100);
344
+ const zeroPct = Fraction.ZERO;
345
+
346
+ test("throws if any of the arguments are negative", () => {
347
+ expect(() =>
348
+ getSwapInput(-1n, 10n, 10n, Fraction.ZERO, Fraction.ZERO, 1000n),
349
+ ).toThrow();
350
+ expect(() =>
351
+ getSwapInput(1n, -10n, 10n, Fraction.ZERO, Fraction.ZERO, 1000n),
352
+ ).toThrow();
353
+ expect(() =>
354
+ getSwapInput(1n, 10n, -10n, Fraction.ZERO, Fraction.ZERO, 1000n),
355
+ ).toThrow();
356
+ expect(() =>
357
+ getSwapInput(
358
+ 1n,
359
+ 10n,
360
+ 10n,
361
+ Fraction.asFraction(-0.1),
362
+ Fraction.ZERO,
363
+ 1000n,
364
+ ),
365
+ ).toThrow();
366
+ });
367
+
368
+ test("throws if fee is greater than or equal 1", () => {
369
+ expect(() =>
370
+ getSwapInput(1n, 10n, 10n, Fraction.asFraction(1), Fraction.ZERO, 1000n),
371
+ ).toThrow();
372
+ expect(() =>
373
+ getSwapInput(
374
+ 1n,
375
+ 10n,
376
+ 10n,
377
+ Fraction.asFraction(1.132),
378
+ Fraction.ZERO,
379
+ 1000n,
380
+ ),
381
+ ).toThrow();
382
+ });
383
+
384
+ test("throws if output is greater than or equal to output reserve", () => {
385
+ expect(() =>
386
+ getSwapInput(
387
+ 10n,
388
+ 10n,
389
+ 10n,
390
+ Fraction.asFraction(0.1),
391
+ Fraction.ZERO,
392
+ 1000n,
393
+ ),
394
+ ).toThrow();
395
+ expect(() =>
396
+ getSwapInput(1n, 1n, 1n, Fraction.asFraction(0.1), Fraction.ZERO, 1000n),
397
+ ).toThrow();
398
+ expect(() =>
399
+ getSwapInput(10n, 1n, 1n, Fraction.asFraction(0.1), Fraction.ZERO, 1000n),
400
+ ).toThrow();
401
+ });
402
+
403
+ test.each([
404
+ // Ideal 0% fee pool
405
+ {
406
+ input: 1n,
407
+ reserves: [1n, 2n],
408
+ fee: zeroPct,
409
+ impact: new Fraction(0n, 2000n),
410
+ },
411
+ {
412
+ input: 100n,
413
+ reserves: [1n, 2n],
414
+ fee: zeroPct,
415
+ impact: new Fraction(0n, 2000n),
416
+ },
417
+ {
418
+ input: 100n,
419
+ reserves: [1n, 100n],
420
+ fee: zeroPct,
421
+ impact: new Fraction(245025n, 445500n),
422
+ },
423
+ {
424
+ input: 100n,
425
+ reserves: [100n, 100n],
426
+ fee: zeroPct,
427
+ impact: new Fraction(400400n, 20020000n),
428
+ },
429
+ {
430
+ input: 50n,
431
+ reserves: [100n, 100n],
432
+ fee: zeroPct,
433
+ impact: new Fraction(200200n, 10010000n),
434
+ },
435
+
436
+ // Simple boundary cases
437
+ {
438
+ input: 10n,
439
+ reserves: [10n, 30n],
440
+ fee: threePct,
441
+ impact: new Fraction(20315n, 200450n),
442
+ },
443
+ {
444
+ input: 100n,
445
+ reserves: [1n, 100n],
446
+ fee: threePct,
447
+ impact: new Fraction(251100n, 445500n),
448
+ },
449
+ {
450
+ input: 100n,
451
+ reserves: [100n, 100n],
452
+ fee: threePct,
453
+ impact: new Fraction(1001000n, 20020000n),
454
+ },
455
+ {
456
+ input: 50n,
457
+ reserves: [100n, 100n],
458
+ fee: threePct,
459
+ impact: new Fraction(600600n, 10010000n),
460
+ },
461
+
462
+ // // Real world examples
463
+ {
464
+ input: 5n,
465
+ reserves: [84159832107n, 123172010729n],
466
+ fee: fivePct,
467
+ impact: new Fraction(168806906319235n, 842673001141955n),
468
+ },
469
+ {
470
+ input: 100n,
471
+ reserves: [84159832107n, 123172010729n],
472
+ fee: fivePct,
473
+ impact: new Fraction(849140270799500n, 16853460022839100n),
474
+ },
475
+ {
476
+ input: 1291591603n,
477
+ reserves: [5753371381n, 672426600000n],
478
+ fee: onePct,
479
+ impact: new Fraction(15118057654414078594677n, 40350603201831427592080n),
480
+ },
481
+ ] as {
482
+ input: bigint;
483
+ reserves: TPair;
484
+ fee: TFractionLike;
485
+ impact: Fraction;
486
+ }[])(
487
+ "gets the correct swap input, given different swap outputs (%#)",
488
+ ({ input, reserves, fee, impact }) => {
489
+ const outcome = getSwapOutput(
490
+ input,
491
+ ...reserves,
492
+ fee,
493
+ Fraction.ZERO,
494
+ 1000n,
495
+ );
496
+ const actual = getSwapInput(
497
+ outcome.output,
498
+ ...reserves,
499
+ fee,
500
+ Fraction.ZERO,
501
+ 1000n,
502
+ );
503
+ const outcomeForActual = getSwapOutput(
504
+ actual.input,
505
+ ...reserves,
506
+ fee,
507
+ Fraction.ZERO,
508
+ 1000n,
509
+ );
510
+ expect(outcome.output).toBe(outcomeForActual.output);
511
+ expect(actual.outputLpFee).toBe(outcomeForActual.outputLpFee);
512
+ expect(actual.nextInputReserve).toBe(outcomeForActual.nextInputReserve);
513
+ expect(actual.nextOutputReserve).toBe(outcomeForActual.nextOutputReserve);
514
+ expect(actual.priceImpact).toEqual(outcomeForActual.priceImpact);
515
+ expect(actual.priceImpact).toStrictEqual(impact);
516
+ },
517
+ );
518
+ });
@@ -1 +1,3 @@
1
1
  export * as ConstantProductPool from "./ConstantProductPool.js";
2
+ export * as StableSwapsPool from "./StableSwapsPool.js";
3
+ export * as SharedPoolMath from "./SharedPoolMath.js";