@reserve-protocol/dtf-rebalance-lib 0.1.0 → 0.1.3

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.
@@ -1,4 +1,4 @@
1
- import { PriceRange, Rebalance, RebalanceLimits, WeightRange } from './types';
1
+ import { PriceRange, Rebalance, RebalanceLimits, WeightRange } from "./types";
2
2
  export declare enum AuctionRound {
3
3
  EJECT = 0,
4
4
  PROGRESS = 1,
@@ -27,17 +27,13 @@ var AuctionRound;
27
27
  */
28
28
  const getTargetBasket = (_initialWeights, _prices, _decimals) => {
29
29
  if (_initialWeights.length != _prices.length) {
30
- throw new Error('length mismatch');
30
+ throw new Error("length mismatch");
31
31
  }
32
32
  const vals = _initialWeights.map((initialWeight, i) => {
33
33
  const price = new decimal_js_light_1.default(_prices[i]);
34
34
  const decimalScale = new decimal_js_light_1.default(`1e${_decimals[i]}`);
35
35
  // {USD/wholeBU} = D27{tok/BU} * {BU/wholeBU} / {tok/wholeTok} / D27 * {USD/wholeTok}
36
- return new decimal_js_light_1.default(initialWeight.spot.toString())
37
- .mul(numbers_1.D18d)
38
- .div(decimalScale)
39
- .div(numbers_1.D27d)
40
- .mul(price);
36
+ return new decimal_js_light_1.default(initialWeight.spot.toString()).mul(numbers_1.D18d).div(decimalScale).div(numbers_1.D27d).mul(price);
41
37
  });
42
38
  const totalValue = vals.reduce((a, b) => a.add(b));
43
39
  // D18{1} = {USD/wholeBU} / {USD/wholeBU} * D18
@@ -61,17 +57,17 @@ exports.getTargetBasket = getTargetBasket;
61
57
  */
62
58
  const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket = [], _folio, _decimals, _prices, _priceError, _finalStageAt, logging) => {
63
59
  if (logging) {
64
- console.log('getOpenAuction', rebalance, _supply, _initialFolio, _targetBasket, _folio, _decimals, _prices, _priceError, _finalStageAt);
60
+ console.log("getOpenAuction", rebalance, _supply, _initialFolio, _targetBasket, _folio, _decimals, _prices, _priceError, _finalStageAt);
65
61
  }
66
62
  if (rebalance.tokens.length != _targetBasket.length ||
67
63
  _targetBasket.length != _folio.length ||
68
64
  _folio.length != _decimals.length ||
69
65
  _decimals.length != _prices.length ||
70
66
  _prices.length != _priceError.length) {
71
- throw new Error('length mismatch');
67
+ throw new Error("length mismatch");
72
68
  }
73
69
  if (_finalStageAt > 1) {
74
- throw new Error('finalStageAt must be less than 1');
70
+ throw new Error("finalStageAt must be less than 1");
75
71
  }
76
72
  // ================================================================
77
73
  // {wholeShare} = {share} / {share/wholeShare}
@@ -96,34 +92,21 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
96
92
  // {wholeTok/wholeBU} = D27{tok/BU} * {BU/wholeBU} / {tok/wholeTok} / D27
97
93
  let weightRanges = rebalance.weights.map((range, i) => {
98
94
  return {
99
- low: new decimal_js_light_1.default(range.low.toString())
100
- .mul(numbers_1.D18d)
101
- .div(decimalScale[i])
102
- .div(numbers_1.D27d),
103
- spot: new decimal_js_light_1.default(range.spot.toString())
104
- .mul(numbers_1.D18d)
105
- .div(decimalScale[i])
106
- .div(numbers_1.D27d),
107
- high: new decimal_js_light_1.default(range.high.toString())
108
- .mul(numbers_1.D18d)
109
- .div(decimalScale[i])
110
- .div(numbers_1.D27d),
95
+ low: new decimal_js_light_1.default(range.low.toString()).mul(numbers_1.D18d).div(decimalScale[i]).div(numbers_1.D27d),
96
+ spot: new decimal_js_light_1.default(range.spot.toString()).mul(numbers_1.D18d).div(decimalScale[i]).div(numbers_1.D27d),
97
+ high: new decimal_js_light_1.default(range.high.toString()).mul(numbers_1.D18d).div(decimalScale[i]).div(numbers_1.D27d),
111
98
  };
112
99
  });
113
100
  const finalStageAt = new decimal_js_light_1.default(_finalStageAt.toString());
114
101
  // ================================================================
115
102
  // calculate ideal spot limit, the actual BU<->share ratio
116
103
  // {USD/wholeShare} = {wholeTok/wholeShare} * {USD/wholeTok}
117
- const shareValue = folio
118
- .map((f, i) => f.mul(prices[i]))
119
- .reduce((a, b) => a.add(b));
104
+ const shareValue = folio.map((f, i) => f.mul(prices[i])).reduce((a, b) => a.add(b));
120
105
  // {USD/wholeBU} = {wholeTok/wholeBU} * {USD/wholeTok}
121
- const buValue = weightRanges
122
- .map((weightRange, i) => weightRange.spot.mul(prices[i]))
123
- .reduce((a, b) => a.add(b));
106
+ const buValue = weightRanges.map((weightRange, i) => weightRange.spot.mul(prices[i])).reduce((a, b) => a.add(b));
124
107
  if (logging) {
125
- console.log('shareValue', shareValue.toString());
126
- console.log('buValue', buValue.toString());
108
+ console.log("shareValue", shareValue.toString());
109
+ console.log("buValue", buValue.toString());
127
110
  }
128
111
  // ================================================================
129
112
  // calculate rebalanceTarget
@@ -144,9 +127,7 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
144
127
  // {wholeTok/wholeShare} = {USD/wholeShare} * {1} / {USD/wholeTok}
145
128
  const balanceExpected = shareValue.mul(targetBasket[i]).div(prices[i]);
146
129
  // {wholeTok/wholeShare} = {wholeTok/wholeBU} * {wholeBU/wholeShare}
147
- const balanceInBU = balanceExpected.gt(actualBalance)
148
- ? actualBalance
149
- : balanceExpected;
130
+ const balanceInBU = balanceExpected.gt(actualBalance) ? actualBalance : balanceExpected;
150
131
  // {USD/wholeShare} = {wholeTok/wholeShare} * {USD/wholeTok}
151
132
  return balanceInBU.mul(prices[i]);
152
133
  })
@@ -158,9 +139,7 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
158
139
  // {wholeTok/wholeShare} = {USD/wholeShare} * {1} / {USD/wholeTok}
159
140
  const balanceExpected = shareValue.mul(targetBasket[i]).div(prices[i]);
160
141
  // {wholeTok/wholeShare} = {wholeTok/wholeBU} * {wholeBU/wholeShare}
161
- const balanceInBU = balanceExpected.gt(initialBalance)
162
- ? initialBalance
163
- : balanceExpected;
142
+ const balanceInBU = balanceExpected.gt(initialBalance) ? initialBalance : balanceExpected;
164
143
  // {USD/wholeShare} = {wholeTok/wholeShare} * {USD/wholeTok}
165
144
  return balanceInBU.mul(prices[i]);
166
145
  })
@@ -176,11 +155,11 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
176
155
  let rebalanceTarget = numbers_1.ONE;
177
156
  let round = AuctionRound.FINAL;
178
157
  if (logging) {
179
- console.log('initialProgression', initialProgression.toString());
180
- console.log('progression', progression.toString());
181
- console.log('relativeProgression', relativeProgression.toString());
182
- console.log('portionBeingEjected', portionBeingEjected.toString());
183
- console.log('finalStageAt', finalStageAt.toString());
158
+ console.log("initialProgression", initialProgression.toString());
159
+ console.log("progression", progression.toString());
160
+ console.log("relativeProgression", relativeProgression.toString());
161
+ console.log("portionBeingEjected", portionBeingEjected.toString());
162
+ console.log("finalStageAt", finalStageAt.toString());
184
163
  }
185
164
  // make it an eject auction if there is 1 bps or more of value to eject
186
165
  if (portionBeingEjected.gte(1e-4)) {
@@ -202,13 +181,13 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
202
181
  }
203
182
  }
204
183
  if (rebalanceTarget.gt(numbers_1.ONE)) {
205
- throw new Error('something has gone very wrong');
184
+ throw new Error("something has gone very wrong");
206
185
  }
207
- if (rebalanceTarget.lt(progression)) {
186
+ if (rebalanceTarget.lt(progression) || numbers_1.ONE.sub(rebalanceTarget).lt(1e-4)) {
208
187
  rebalanceTarget = numbers_1.ONE;
209
188
  }
210
189
  if (logging) {
211
- console.log('rebalanceTarget', rebalanceTarget.toString());
190
+ console.log("rebalanceTarget", rebalanceTarget.toString());
212
191
  }
213
192
  // {1}
214
193
  const delta = numbers_1.ONE.sub(rebalanceTarget);
@@ -224,9 +203,9 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
224
203
  };
225
204
  if (round == AuctionRound.EJECT && rebalanceTarget.eq(numbers_1.ONE)) {
226
205
  // aim 1% higher if executed permissonlessly
227
- newLimits.spot += newLimits.spot * 1n / 100n;
206
+ newLimits.spot += (newLimits.spot * 1n) / 100n;
228
207
  // leave 10% room to increase low in the future if ejection leaves dust behind
229
- newLimits.high += newLimits.high * 10n / 100n;
208
+ newLimits.high += (newLimits.high * 10n) / 100n;
230
209
  }
231
210
  // low
232
211
  if (newLimits.low < rebalance.limits.low) {
@@ -250,7 +229,7 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
250
229
  newLimits.high = rebalance.limits.high;
251
230
  }
252
231
  if (logging) {
253
- console.log('newLimits', newLimits);
232
+ console.log("newLimits", newLimits);
254
233
  }
255
234
  // ================================================================
256
235
  // get new weights, constrained by extremes
@@ -263,10 +242,7 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
263
242
  // D27{tok/BU}
264
243
  const newWeights = rebalance.weights.map((weightRange, i) => {
265
244
  // {wholeTok/wholeBU} = {USD/wholeShare} * {1} / {wholeBU/wholeShare} / {USD/wholeTok}
266
- const idealWeight = shareValue
267
- .mul(targetBasket[i])
268
- .div(actualLimits.spot)
269
- .div(prices[i]);
245
+ const idealWeight = shareValue.mul(targetBasket[i]).div(actualLimits.spot).div(prices[i]);
270
246
  // D27{tok/BU} = {wholeTok/wholeBU} * D27 * {tok/wholeTok} / {BU/wholeBU}
271
247
  const newWeightsD27 = {
272
248
  low: (0, numbers_1.bn)(idealWeight
@@ -283,9 +259,9 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
283
259
  };
284
260
  if (round == AuctionRound.EJECT && rebalanceTarget.eq(numbers_1.ONE)) {
285
261
  // aim 1% higher if executed permissonlessly
286
- newWeightsD27.spot += newWeightsD27.spot * 1n / 100n;
262
+ newWeightsD27.spot += (newWeightsD27.spot * 1n) / 100n;
287
263
  // leave 10% room to increase low in the future if ejection leaves dust behind
288
- newWeightsD27.high += newWeightsD27.high * 10n / 100n;
264
+ newWeightsD27.high += (newWeightsD27.high * 10n) / 100n;
289
265
  }
290
266
  if (newWeightsD27.low < weightRange.low) {
291
267
  newWeightsD27.low = weightRange.low;
@@ -308,7 +284,7 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
308
284
  return newWeightsD27;
309
285
  });
310
286
  if (logging) {
311
- console.log('newWeights', newWeights);
287
+ console.log("newWeights", newWeights);
312
288
  }
313
289
  // ================================================================
314
290
  // get new prices, constrained by extremes
@@ -317,7 +293,7 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
317
293
  // revert if price out of bounds
318
294
  const spotPrice = (0, numbers_1.bn)(prices[i].mul(numbers_1.D27d).div(decimalScale[i]));
319
295
  if (spotPrice < initialPrice.low || spotPrice > initialPrice.high) {
320
- throw new Error('spot price out of bounds! auction launcher MUST closeRebalance to prevent loss!');
296
+ throw new Error("spot price out of bounds! auction launcher MUST closeRebalance to prevent loss!");
321
297
  }
322
298
  if (rebalance.priceControl == types_1.PriceControl.NONE) {
323
299
  return initialPrice;
@@ -342,38 +318,26 @@ const getOpenAuction = (rebalance, _supply, _initialFolio = [], _targetBasket =
342
318
  pricesD27.high = initialPrice.high;
343
319
  }
344
320
  if (pricesD27.low == pricesD27.high && priceError[i].gt(numbers_1.ZERO)) {
345
- throw new Error('no price range');
321
+ throw new Error("no price range");
346
322
  }
347
323
  return pricesD27;
348
324
  });
349
325
  if (logging) {
350
- console.log('newPrices', newPrices);
326
+ console.log("newPrices", newPrices);
351
327
  }
352
328
  // ================================================================
353
329
  // calculate metrics
354
330
  // {USD} = {1} * {USD/wholeShare} * {wholeShare}
355
- const valueBeingTraded = rebalanceTarget
356
- .sub(progression)
357
- .mul(shareValue)
358
- .mul(supply);
331
+ const valueBeingTraded = rebalanceTarget.sub(progression).mul(shareValue).mul(supply);
359
332
  const surplusTokens = [];
360
333
  const deficitTokens = [];
361
334
  // update Decimal weightRanges
362
335
  // {wholeTok/wholeBU} = D27{tok/BU} * {BU/wholeBU} / {tok/wholeTok} / D27
363
336
  weightRanges = newWeights.map((range, i) => {
364
337
  return {
365
- low: new decimal_js_light_1.default(range.low.toString())
366
- .mul(numbers_1.D18d)
367
- .div(decimalScale[i])
368
- .div(numbers_1.D27d),
369
- spot: new decimal_js_light_1.default(range.spot.toString())
370
- .mul(numbers_1.D18d)
371
- .div(decimalScale[i])
372
- .div(numbers_1.D27d),
373
- high: new decimal_js_light_1.default(range.high.toString())
374
- .mul(numbers_1.D18d)
375
- .div(decimalScale[i])
376
- .div(numbers_1.D27d),
338
+ low: new decimal_js_light_1.default(range.low.toString()).mul(numbers_1.D18d).div(decimalScale[i]).div(numbers_1.D27d),
339
+ spot: new decimal_js_light_1.default(range.spot.toString()).mul(numbers_1.D18d).div(decimalScale[i]).div(numbers_1.D27d),
340
+ high: new decimal_js_light_1.default(range.high.toString()).mul(numbers_1.D18d).div(decimalScale[i]).div(numbers_1.D27d),
377
341
  };
378
342
  });
379
343
  rebalance.tokens.forEach((token, i) => {
package/package.json CHANGED
@@ -1,21 +1,23 @@
1
1
  {
2
2
  "name": "@reserve-protocol/dtf-rebalance-lib",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "description": "Rebalancing library for DTFs in typescript",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "files": [
9
9
  "dist",
10
- "src",
11
10
  "LICENSE.md",
12
11
  "README.md"
13
12
  ],
14
13
  "scripts": {
15
14
  "clean": "rm -rf dist",
16
- "build": "npm run clean && tsc",
15
+ "compile": "hardhat compile",
16
+ "build": "npm run clean && tsc --project tsconfig.build.json",
17
17
  "prepublishOnly": "npm run build",
18
- "test": "tsc --project tsconfig.test.json && node --test dist/test/*.test.js"
18
+ "test": "npm run test:unit && npm run test:e2e",
19
+ "test:unit": "node --test --require ts-node/register test/unit/*.test.ts",
20
+ "test:e2e": "hardhat test test/e2e/*.test.ts --bail"
19
21
  },
20
22
  "publishConfig": {
21
23
  "access": "public"
@@ -39,6 +41,14 @@
39
41
  "decimal.js-light": "^2.5.1"
40
42
  },
41
43
  "devDependencies": {
44
+ "@nomicfoundation/hardhat-toolbox": "^5.0.0",
45
+ "@openzeppelin/contracts": "^5.3.0",
46
+ "@reserve-protocol/reserve-index-dtf": "github:reserve-protocol/reserve-index-dtf#a240822d7a02520def5564668ac7699b0520bdae",
47
+ "@types/node": "^20.0.0",
48
+ "dotenv": "^16.5.0",
49
+ "hardhat": "^2.24.1",
50
+ "prettier": "^3.5.3",
51
+ "ts-node": "^10.9.2",
42
52
  "typescript": "^5.8.3"
43
53
  }
44
54
  }
package/src/index.ts DELETED
@@ -1,5 +0,0 @@
1
- export * from './types'
2
- export * from './numbers'
3
- export * from './utils'
4
- export * from './open-auction'
5
- export * from './start-rebalance'
package/src/numbers.ts DELETED
@@ -1,17 +0,0 @@
1
- import Decimal from 'decimal.js-light'
2
-
3
- export const D27n: bigint = 10n ** 27n
4
- export const D18n: bigint = 10n ** 18n
5
- export const D9n: bigint = 10n ** 9n
6
-
7
- export const D27d: Decimal = new Decimal('1e27')
8
- export const D18d: Decimal = new Decimal('1e18')
9
- export const D9d: Decimal = new Decimal('1e9')
10
-
11
- export const ZERO = new Decimal('0')
12
- export const ONE = new Decimal('1')
13
- export const TWO = new Decimal('2')
14
-
15
- export const bn = (str: string | Decimal): bigint => {
16
- return BigInt(new Decimal(str).toFixed(0))
17
- }
@@ -1,557 +0,0 @@
1
- import Decimal from 'decimal.js-light'
2
-
3
- import { bn, D18d, D27d, ONE, ZERO } from './numbers'
4
-
5
- import {
6
- PriceControl,
7
- PriceRange,
8
- Rebalance,
9
- RebalanceLimits,
10
- WeightRange,
11
- } from './types'
12
-
13
- // Call `getOpenAuction()` to get the current auction round
14
- export enum AuctionRound {
15
- EJECT = 0,
16
- PROGRESS = 1,
17
- FINAL = 2,
18
- }
19
-
20
- /**
21
- * Useful metrics to use to visualize things
22
- *
23
- * @param initialProgression {1} The progression the Folio had when the auction was first proposed
24
- * @param absoluteProgression {1} The progression of the auction on an absolute scale
25
- * @param relativeProgression {1} The relative progression of the auction
26
- * @param target {1} The target of the auction on an absolute scale
27
- * @param auctionSize {USD} The total value on sale in the auction
28
- * @param surplusTokens The list of tokens in surplus
29
- * @param deficitTokens The list of tokens in deficit
30
- */
31
- export interface AuctionMetrics {
32
- round: AuctionRound
33
- initialProgression: number
34
- absoluteProgression: number
35
- relativeProgression: number
36
- target: number
37
- auctionSize: number
38
- surplusTokens: string[]
39
- deficitTokens: string[]
40
- }
41
-
42
- // All the args needed to call `folio.openAuction()`
43
- export interface OpenAuctionArgs {
44
- rebalanceNonce: bigint
45
- tokens: string[]
46
- newWeights: WeightRange[]
47
- newPrices: PriceRange[]
48
- newLimits: RebalanceLimits
49
- }
50
-
51
- /**
52
- * Generator for the `targetBasket` parameter
53
- *
54
- * Depending on the usecase, pass either:
55
- * - TRACKING: CURRENT prices
56
- * - NATIVE: HISTORICAL prices
57
- *
58
- * @param _initialWeights D27{tok/BU} The initial historical weights emitted in the RebalanceStarted event
59
- * @param _prices {USD/wholeTok} either CURRENT or HISTORICAL prices
60
- * @returns D18{1} The target basket
61
- */
62
- export const getTargetBasket = (
63
- _initialWeights: WeightRange[],
64
- _prices: number[],
65
- _decimals: bigint[]
66
- ): bigint[] => {
67
- if (_initialWeights.length != _prices.length) {
68
- throw new Error('length mismatch')
69
- }
70
-
71
- const vals = _initialWeights.map((initialWeight: WeightRange, i: number) => {
72
- const price = new Decimal(_prices[i])
73
- const decimalScale = new Decimal(`1e${_decimals[i]}`)
74
-
75
- // {USD/wholeBU} = D27{tok/BU} * {BU/wholeBU} / {tok/wholeTok} / D27 * {USD/wholeTok}
76
- return new Decimal(initialWeight.spot.toString())
77
- .mul(D18d)
78
- .div(decimalScale)
79
- .div(D27d)
80
- .mul(price)
81
- })
82
-
83
- const totalValue = vals.reduce((a, b) => a.add(b))
84
-
85
- // D18{1} = {USD/wholeBU} / {USD/wholeBU} * D18
86
- return vals.map((val) => bn(val.div(totalValue).mul(D18d)))
87
- }
88
-
89
- /**
90
- * Get the values needed to call `folio.openAuction()` as the AUCTION_LAUNCHER
91
- *
92
- * Non-AUCTION_LAUNCHERs should use `folio.openAuctionUnrestricted()`
93
- *
94
- * @param rebalance The result of calling folio.getRebalance()
95
- * @param _supply {share} The totalSupply() of the basket, today
96
- * @param _initialFolio D18{tok/share} Initial balances per share, e.g result of folio.toAssets(1e18, 0) at time rebalance was first proposed
97
- * @param _targetBasket D18{1} Result of calling `getTargetBasket()`
98
- * @param _folio D18{tok/share} Current ratio of token per share, e.g result of folio.toAssets(1e18, 0)
99
- * @param _decimals Decimals of each token
100
- * @param _prices {USD/wholeTok} USD prices for each *whole* token
101
- * @param _priceError {1} Price error to use for each token during auction pricing; should be smaller than price error during startRebalance
102
- * @param _finalStageAt {1} The % rebalanced from the initial Folio to determine when is the final stage of the rebalance
103
- */
104
- export const getOpenAuction = (
105
- rebalance: Rebalance,
106
- _supply: bigint,
107
- _initialFolio: bigint[] = [],
108
- _targetBasket: bigint[] = [],
109
- _folio: bigint[],
110
- _decimals: bigint[],
111
- _prices: number[],
112
- _priceError: number[],
113
- _finalStageAt: number,
114
- logging?: boolean
115
- ): [OpenAuctionArgs, AuctionMetrics] => {
116
- if (logging) {
117
- console.log('getOpenAuction', rebalance, _supply, _initialFolio, _targetBasket, _folio, _decimals, _prices, _priceError, _finalStageAt)
118
- }
119
-
120
- if (
121
- rebalance.tokens.length != _targetBasket.length ||
122
- _targetBasket.length != _folio.length ||
123
- _folio.length != _decimals.length ||
124
- _decimals.length != _prices.length ||
125
- _prices.length != _priceError.length
126
- ) {
127
- throw new Error('length mismatch')
128
- }
129
-
130
- if (_finalStageAt > 1) {
131
- throw new Error('finalStageAt must be less than 1')
132
- }
133
-
134
- // ================================================================
135
-
136
- // {wholeShare} = {share} / {share/wholeShare}
137
- const supply = new Decimal(_supply.toString()).div(D18d)
138
-
139
- // {1} = D18{1} / D18
140
- const targetBasket = _targetBasket.map((a) =>
141
- new Decimal(a.toString()).div(D18d)
142
- )
143
-
144
- // {USD/wholeTok}
145
- const prices = _prices.map((a) => new Decimal(a))
146
- for (let i = 0; i < prices.length; i++) {
147
- if (prices[i].eq(ZERO)) {
148
- throw new Error(`missing price for token ${rebalance.tokens[i]}`)
149
- }
150
- }
151
-
152
- // {1}
153
- const priceError = _priceError.map((a) => new Decimal(a.toString()))
154
-
155
- // {tok/wholeTok}
156
- const decimalScale = _decimals.map((a) => new Decimal(`1e${a}`))
157
-
158
- // {wholeTok/wholeShare} = D18{tok/share} * {share/wholeShare} / {tok/wholeTok} / D18
159
- const initialFolio = _initialFolio.map((c: bigint, i: number) =>
160
- new Decimal(c.toString()).div(decimalScale[i])
161
- )
162
-
163
- // {wholeTok/wholeShare} = D18{tok/share} * {share/wholeShare} / {tok/wholeTok} / D18
164
- const folio = _folio.map((c: bigint, i: number) =>
165
- new Decimal(c.toString()).div(decimalScale[i])
166
- )
167
-
168
- // {wholeTok/wholeBU} = D27{tok/BU} * {BU/wholeBU} / {tok/wholeTok} / D27
169
- let weightRanges = rebalance.weights.map((range: WeightRange, i: number) => {
170
- return {
171
- low: new Decimal(range.low.toString())
172
- .mul(D18d)
173
- .div(decimalScale[i])
174
- .div(D27d),
175
- spot: new Decimal(range.spot.toString())
176
- .mul(D18d)
177
- .div(decimalScale[i])
178
- .div(D27d),
179
- high: new Decimal(range.high.toString())
180
- .mul(D18d)
181
- .div(decimalScale[i])
182
- .div(D27d),
183
- }
184
- })
185
-
186
- const finalStageAt = new Decimal(_finalStageAt.toString())
187
-
188
-
189
- // ================================================================
190
-
191
- // calculate ideal spot limit, the actual BU<->share ratio
192
-
193
- // {USD/wholeShare} = {wholeTok/wholeShare} * {USD/wholeTok}
194
- const shareValue = folio
195
- .map((f: Decimal, i: number) => f.mul(prices[i]))
196
- .reduce((a, b) => a.add(b))
197
-
198
- // {USD/wholeBU} = {wholeTok/wholeBU} * {USD/wholeTok}
199
- const buValue = weightRanges
200
- .map((weightRange, i) => weightRange.spot.mul(prices[i]))
201
- .reduce((a, b) => a.add(b))
202
-
203
- if (logging) {
204
- console.log('shareValue', shareValue.toString())
205
- console.log('buValue', buValue.toString())
206
- }
207
-
208
- // ================================================================
209
-
210
- // calculate rebalanceTarget
211
-
212
- const ejectionIndices: number[] = []
213
- for (let i = 0; i < rebalance.weights.length; i++) {
214
- if (rebalance.weights[i].spot == 0n) {
215
- ejectionIndices.push(i)
216
- }
217
- }
218
-
219
- // {1} = {wholeTok/wholeShare} * {USD/wholeTok} / {USD/wholeShare}
220
- const portionBeingEjected = ejectionIndices
221
- .map((i) => folio[i].mul(prices[i]))
222
- .reduce((a, b) => a.add(b), ZERO)
223
- .div(shareValue)
224
-
225
-
226
- // {1} = {USD/wholeShare} / {USD/wholeShare}
227
- let progression = folio
228
- .map((actualBalance, i) => {
229
- // {wholeTok/wholeShare} = {USD/wholeShare} * {1} / {USD/wholeTok}
230
- const balanceExpected = shareValue.mul(targetBasket[i]).div(prices[i])
231
-
232
- // {wholeTok/wholeShare} = {wholeTok/wholeBU} * {wholeBU/wholeShare}
233
- const balanceInBU = balanceExpected.gt(actualBalance)
234
- ? actualBalance
235
- : balanceExpected
236
-
237
- // {USD/wholeShare} = {wholeTok/wholeShare} * {USD/wholeTok}
238
- return balanceInBU.mul(prices[i])
239
- })
240
- .reduce((a, b) => a.add(b))
241
- .div(shareValue)
242
-
243
- // {1} = {USD/wholeShare} / {USD/wholeShare}
244
- const initialProgression = initialFolio
245
- .map((initialBalance, i) => {
246
- // {wholeTok/wholeShare} = {USD/wholeShare} * {1} / {USD/wholeTok}
247
- const balanceExpected = shareValue.mul(targetBasket[i]).div(prices[i])
248
-
249
- // {wholeTok/wholeShare} = {wholeTok/wholeBU} * {wholeBU/wholeShare}
250
- const balanceInBU = balanceExpected.gt(initialBalance)
251
- ? initialBalance
252
- : balanceExpected
253
-
254
- // {USD/wholeShare} = {wholeTok/wholeShare} * {USD/wholeTok}
255
- return balanceInBU.mul(prices[i])
256
- })
257
- .reduce((a, b) => a.add(b))
258
- .div(shareValue)
259
-
260
- if (progression < initialProgression) {
261
- progression = initialProgression // don't go backwards
262
- }
263
-
264
-
265
- // {1} = {1} / {1}
266
- const relativeProgression = initialProgression.eq(ONE)
267
- ? ONE
268
- : progression.sub(initialProgression).div(ONE.sub(initialProgression))
269
-
270
- let rebalanceTarget = ONE
271
- let round: AuctionRound = AuctionRound.FINAL
272
-
273
- if (logging) {
274
- console.log('initialProgression', initialProgression.toString())
275
- console.log('progression', progression.toString())
276
- console.log('relativeProgression', relativeProgression.toString())
277
- console.log('portionBeingEjected', portionBeingEjected.toString())
278
- console.log('finalStageAt', finalStageAt.toString())
279
- }
280
-
281
- // make it an eject auction if there is 1 bps or more of value to eject
282
- if (portionBeingEjected.gte(1e-4)) {
283
- round = AuctionRound.EJECT
284
-
285
- if (relativeProgression.lt(finalStageAt.sub(0.02))) {
286
- rebalanceTarget = progression.add(portionBeingEjected.mul(1.1)) // set rebalanceTarget to 10% more than needed to ensure ejection completes
287
-
288
- // do not finish trading yet
289
- if (rebalanceTarget.gte(ONE)) {
290
- rebalanceTarget = initialProgression.add(ONE.sub(initialProgression).mul(finalStageAt))
291
- }
292
- }
293
- } else if (relativeProgression.lt(finalStageAt.sub(0.02))) {
294
- // wiggle room to prevent having to re-run an auction at the same stage after price movement
295
- round = AuctionRound.PROGRESS
296
-
297
- rebalanceTarget = initialProgression.add(ONE.sub(initialProgression).mul(finalStageAt))
298
- if (rebalanceTarget.eq(ONE)) {
299
- round = AuctionRound.FINAL
300
- }
301
- }
302
-
303
- if (rebalanceTarget.gt(ONE)) {
304
- throw new Error('something has gone very wrong')
305
- }
306
-
307
- if (rebalanceTarget.lt(progression)) {
308
- rebalanceTarget = ONE
309
- }
310
-
311
- if (logging) {
312
- console.log('rebalanceTarget', rebalanceTarget.toString())
313
- }
314
-
315
- // {1}
316
- const delta = ONE.sub(rebalanceTarget)
317
-
318
- // ================================================================
319
-
320
- // get new limits, constrained by extremes
321
-
322
- // {wholeBU/wholeShare} = {USD/wholeShare} / {USD/wholeBU}
323
- const spotLimit = shareValue.div(buValue)
324
-
325
- // D18{BU/share} = {wholeBU/wholeShare} * D18 * {1}
326
- const newLimits = {
327
- low: bn(spotLimit.sub(spotLimit.mul(delta)).mul(D18d)),
328
- spot: bn(spotLimit.mul(D18d)),
329
- high: bn(spotLimit.add(spotLimit.mul(delta)).mul(D18d)),
330
- }
331
-
332
- if (round == AuctionRound.EJECT && rebalanceTarget.eq(ONE)) {
333
- // aim 1% higher if executed permissonlessly
334
- newLimits.spot += newLimits.spot * 1n / 100n
335
-
336
- // leave 10% room to increase low in the future if ejection leaves dust behind
337
- newLimits.high += newLimits.high * 10n / 100n
338
- }
339
-
340
- // low
341
- if (newLimits.low < rebalance.limits.low) {
342
- newLimits.low = rebalance.limits.low
343
- }
344
- if (newLimits.low > rebalance.limits.high) {
345
- newLimits.low = rebalance.limits.high
346
- }
347
-
348
- // spot
349
- if (newLimits.spot < rebalance.limits.low) {
350
- newLimits.spot = rebalance.limits.low
351
- }
352
- if (newLimits.spot > rebalance.limits.high) {
353
- newLimits.spot = rebalance.limits.high
354
- }
355
-
356
- // high
357
- if (newLimits.high < rebalance.limits.low) {
358
- newLimits.high = rebalance.limits.low
359
- }
360
- if (newLimits.high > rebalance.limits.high) {
361
- newLimits.high = rebalance.limits.high
362
- }
363
-
364
- if (logging) {
365
- console.log('newLimits', newLimits)
366
- }
367
-
368
- // ================================================================
369
-
370
- // get new weights, constrained by extremes
371
-
372
- // {wholeBU/wholeShare} = D18{BU/share} / D18
373
- const actualLimits = {
374
- low: new Decimal(newLimits.low.toString()).div(D18d),
375
- spot: new Decimal(newLimits.spot.toString()).div(D18d),
376
- high: new Decimal(newLimits.high.toString()).div(D18d),
377
- }
378
-
379
- // D27{tok/BU}
380
- const newWeights = rebalance.weights.map((weightRange, i) => {
381
- // {wholeTok/wholeBU} = {USD/wholeShare} * {1} / {wholeBU/wholeShare} / {USD/wholeTok}
382
- const idealWeight = shareValue
383
- .mul(targetBasket[i])
384
- .div(actualLimits.spot)
385
- .div(prices[i])
386
-
387
- // D27{tok/BU} = {wholeTok/wholeBU} * D27 * {tok/wholeTok} / {BU/wholeBU}
388
- const newWeightsD27 = {
389
- low: bn(
390
- idealWeight
391
- .mul(rebalanceTarget.div(actualLimits.low.div(actualLimits.spot))) // add remaining delta into weight
392
- .mul(D27d)
393
- .mul(decimalScale[i])
394
- .div(D18d)
395
- ),
396
- spot: bn(idealWeight.mul(D27d).mul(decimalScale[i]).div(D18d)),
397
- high: bn(
398
- idealWeight
399
- .mul(ONE.add(delta).div(actualLimits.high.div(actualLimits.spot))) // add remaining delta into weight
400
- .mul(D27d)
401
- .mul(decimalScale[i])
402
- .div(D18d)
403
- ),
404
- }
405
-
406
- if (round == AuctionRound.EJECT && rebalanceTarget.eq(ONE)) {
407
- // aim 1% higher if executed permissonlessly
408
- newWeightsD27.spot += newWeightsD27.spot * 1n / 100n
409
-
410
- // leave 10% room to increase low in the future if ejection leaves dust behind
411
- newWeightsD27.high += newWeightsD27.high * 10n / 100n
412
- }
413
-
414
- if (newWeightsD27.low < weightRange.low) {
415
- newWeightsD27.low = weightRange.low
416
- } else if (newWeightsD27.low > weightRange.high) {
417
- newWeightsD27.low = weightRange.high
418
- }
419
-
420
- if (newWeightsD27.spot < weightRange.low) {
421
- newWeightsD27.spot = weightRange.low
422
- } else if (newWeightsD27.spot > weightRange.high) {
423
- newWeightsD27.spot = weightRange.high
424
- }
425
-
426
- if (newWeightsD27.high < weightRange.low) {
427
- newWeightsD27.high = weightRange.low
428
- } else if (newWeightsD27.high > weightRange.high) {
429
- newWeightsD27.high = weightRange.high
430
- }
431
-
432
- return newWeightsD27
433
- })
434
-
435
- if (logging) {
436
- console.log('newWeights', newWeights)
437
- }
438
-
439
- // ================================================================
440
-
441
- // get new prices, constrained by extremes
442
-
443
-
444
- // D27{USD/tok}
445
- const newPrices = rebalance.initialPrices.map((initialPrice, i) => {
446
- // revert if price out of bounds
447
- const spotPrice = bn(prices[i].mul(D27d).div(decimalScale[i]))
448
- if (spotPrice < initialPrice.low || spotPrice > initialPrice.high) {
449
- throw new Error('spot price out of bounds! auction launcher MUST closeRebalance to prevent loss!')
450
- }
451
-
452
- if (rebalance.priceControl == PriceControl.NONE) {
453
- return initialPrice
454
- }
455
-
456
- // D27{USD/tok} = {USD/wholeTok} * D27 / {tok/wholeTok}
457
- const pricesD27 = {
458
- low: bn(
459
- prices[i].mul(ONE.sub(priceError[i])).mul(D27d).div(decimalScale[i])
460
- ),
461
- high: bn(
462
- prices[i].div(ONE.sub(priceError[i])).mul(D27d).div(decimalScale[i])
463
- ),
464
- }
465
-
466
-
467
- // low
468
- if (pricesD27.low < initialPrice.low) {
469
- pricesD27.low = initialPrice.low
470
- }
471
- if (pricesD27.low > initialPrice.high) {
472
- pricesD27.low = initialPrice.high
473
- }
474
-
475
- // high
476
- if (pricesD27.high < initialPrice.low) {
477
- pricesD27.high = initialPrice.low
478
- }
479
- if (pricesD27.high > initialPrice.high) {
480
- pricesD27.high = initialPrice.high
481
- }
482
-
483
- if (pricesD27.low == pricesD27.high && priceError[i].gt(ZERO)) {
484
- throw new Error('no price range')
485
- }
486
-
487
- return pricesD27
488
- })
489
-
490
- if (logging) {
491
- console.log('newPrices', newPrices)
492
- }
493
-
494
- // ================================================================
495
-
496
- // calculate metrics
497
-
498
- // {USD} = {1} * {USD/wholeShare} * {wholeShare}
499
- const valueBeingTraded = rebalanceTarget
500
- .sub(progression)
501
- .mul(shareValue)
502
- .mul(supply)
503
-
504
- const surplusTokens: string[] = []
505
- const deficitTokens: string[] = []
506
-
507
- // update Decimal weightRanges
508
- // {wholeTok/wholeBU} = D27{tok/BU} * {BU/wholeBU} / {tok/wholeTok} / D27
509
- weightRanges = newWeights.map((range, i) => {
510
- return {
511
- low: new Decimal(range.low.toString())
512
- .mul(D18d)
513
- .div(decimalScale[i])
514
- .div(D27d),
515
- spot: new Decimal(range.spot.toString())
516
- .mul(D18d)
517
- .div(decimalScale[i])
518
- .div(D27d),
519
- high: new Decimal(range.high.toString())
520
- .mul(D18d)
521
- .div(decimalScale[i])
522
- .div(D27d),
523
- }
524
- })
525
-
526
- rebalance.tokens.forEach((token, i) => {
527
- // {wholeTok/wholeShare} = {wholeTok/wholeBU} * {wholeBU/wholeShare}
528
- const buyUpTo = weightRanges[i].low.mul(actualLimits.low)
529
- const sellDownTo = weightRanges[i].high.mul(actualLimits.high)
530
-
531
- if (folio[i].lt(buyUpTo)) {
532
- deficitTokens.push(token)
533
- } else if (folio[i].gt(sellDownTo)) {
534
- surplusTokens.push(token)
535
- }
536
- })
537
-
538
- return [
539
- {
540
- rebalanceNonce: rebalance.nonce,
541
- tokens: rebalance.tokens, // full set of tokens, not pruned to the active buy/sells
542
- newWeights: newWeights,
543
- newPrices: newPrices,
544
- newLimits: newLimits,
545
- },
546
- {
547
- round: round,
548
- initialProgression: initialProgression.toNumber(),
549
- absoluteProgression: progression.toNumber(),
550
- relativeProgression: relativeProgression.toNumber(),
551
- target: rebalanceTarget.toNumber(),
552
- auctionSize: valueBeingTraded.toNumber(),
553
- surplusTokens: surplusTokens,
554
- deficitTokens: deficitTokens,
555
- },
556
- ]
557
- }
@@ -1,170 +0,0 @@
1
- import Decimal from 'decimal.js-light'
2
-
3
- import { bn, D18d, D27d, ONE, ZERO } from './numbers'
4
-
5
- import { PriceRange, RebalanceLimits, WeightRange } from './types'
6
-
7
- // Partial set of the args needed to call `startRebalance()`
8
- export interface StartRebalanceArgsPartial {
9
- // tokens: string[]
10
- weights: WeightRange[]
11
- prices: PriceRange[]
12
- limits: RebalanceLimits
13
- // auctionLauncherWindow: bigint
14
- // ttl: bigint
15
- }
16
-
17
- /**
18
- * Get the arguments needed to call startRebalance
19
- *
20
- * The `tokens` argument should be paired with the two return values and passed to `startRebalance()`
21
- *
22
- * @param _supply {share}
23
- * @param tokens Addresses of tokens in the basket
24
- * @param _folio D18{tok/share} Folio of the basket
25
- * @param decimals Decimals of each token
26
- * @param _targetBasket D18{1} Ideal basket
27
- * @param _prices {USD/wholeTok} USD prices for each *whole* token
28
- * @param _priceError {1} Price error per token to use in the rebalanc; should be larger than price error during openAuction
29
- * @param _dtfPrice {USD/wholeShare} DTF price
30
- * @param weightControl TRACKING=false, NATIVE=true
31
- */
32
- export const getStartRebalance = (
33
- _supply: bigint,
34
- tokens: string[],
35
- _folio: bigint[],
36
- decimals: bigint[],
37
- _targetBasket: bigint[],
38
- _prices: number[],
39
- _priceError: number[],
40
- weightControl: boolean,
41
- logging?: boolean
42
- ): StartRebalanceArgsPartial => {
43
- if (logging) {
44
- console.log('getStartRebalance', _supply, tokens, _folio, decimals, _targetBasket, _prices, _priceError, weightControl)
45
- }
46
-
47
- // {wholeTok/wholeShare} = D18{tok/share} * {share/wholeShare} / {tok/wholeTok} / D18
48
- const folio = _folio.map((c: bigint, i: number) =>
49
- new Decimal(c.toString()).div(new Decimal(`1e${decimals[i]}`))
50
- )
51
-
52
- // convert price number inputs to bigints
53
-
54
- // {USD/wholeTok}
55
- const prices = _prices.map((a) => new Decimal(a.toString()))
56
- for (let i = 0; i < prices.length; i++) {
57
- if (prices[i].eq(ZERO)) {
58
- throw new Error(`missing price for token ${tokens[i]}`)
59
- }
60
- }
61
-
62
- // {1} = D18{1} / D18
63
- const targetBasket = _targetBasket.map((a) =>
64
- new Decimal(a.toString()).div(D18d)
65
- )
66
-
67
- // {1}
68
- const priceError = _priceError.map((a) => new Decimal(a.toString()))
69
-
70
- // ================================================================
71
-
72
- const newWeights: WeightRange[] = []
73
- const newPrices: PriceRange[] = []
74
- const newLimits: RebalanceLimits = {
75
- low: bn('1e18'),
76
- spot: bn('1e18'),
77
- high: bn('1e18'),
78
- }
79
-
80
- // ================================================================
81
-
82
- for (let i = 0; i < tokens.length; i++) {
83
- if (priceError[i].gte(ONE)) {
84
- throw new Error('cannot defer prices')
85
- }
86
-
87
- // === newWeights ===
88
-
89
- // {USD/wholeShare} = {wholeTok/wholeShare} * {USD/wholeTok}
90
- const dtfPrice = folio.map((f: Decimal, i: number) => f.mul(prices[i])).reduce((a: Decimal, b: Decimal) => a.add(b))
91
-
92
- // {wholeTok/wholeShare} = {1} * {USD/wholeShare} / {USD/wholeTok}
93
- const spotWeight = targetBasket[i].mul(dtfPrice).div(prices[i])
94
-
95
-
96
- // D27{tok/share}{wholeShare/wholeTok} = D27 * {tok/wholeTok} / {share/wholeShare}
97
- const limitMultiplier = D27d.mul(new Decimal(`1e${decimals[i]}`)).div(D18d)
98
-
99
- if (logging) {
100
- console.log('limitMultiplier', limitMultiplier.toString())
101
- }
102
-
103
- if (!weightControl) {
104
- // D27{tok/BU} = {wholeTok/wholeShare} * D27{tok/share}{wholeShare/wholeTok} / {BU/share}
105
- newWeights.push({
106
- low: bn(spotWeight.mul(limitMultiplier)),
107
- spot: bn(spotWeight.mul(limitMultiplier)),
108
- high: bn(spotWeight.mul(limitMultiplier)),
109
- })
110
- } else {
111
- // NATIVE case
112
-
113
- // {wholeTok/wholeShare} = {wholeTok/wholeShare} / {1}
114
- const lowWeight = spotWeight.mul(ONE.div(ONE.add(priceError[i])))
115
- const highWeight = spotWeight.mul(ONE.add(priceError[i]))
116
-
117
-
118
- // D27{tok/share} = {wholeTok/wholeShare} * D27{tok/share}{wholeShare/wholeTok} / {BU/share}
119
- newWeights.push({
120
- low: bn(lowWeight.mul(limitMultiplier)),
121
- spot: bn(spotWeight.mul(limitMultiplier)),
122
- high: bn(highWeight.mul(limitMultiplier)),
123
- })
124
- }
125
-
126
- // === newPrices ===
127
-
128
- // D27{wholeTok/tok} = D27 / {tok/wholeTok}
129
- const priceMultiplier = D27d.div(new Decimal(`1e${decimals[i]}`))
130
-
131
- // {USD/wholeTok} = {USD/wholeTok} * {1}
132
- const lowPrice = prices[i].mul(ONE.sub(priceError[i]))
133
- const highPrice = prices[i].mul(ONE.add(priceError[i]))
134
-
135
-
136
- // D27{USD/tok} = {USD/wholeTok} * D27{wholeTok/tok}
137
- newPrices.push({
138
- low: bn(lowPrice.mul(priceMultiplier)),
139
- high: bn(highPrice.mul(priceMultiplier)),
140
- })
141
- }
142
-
143
- // update low/high for tracking DTFs
144
- if (!weightControl) {
145
- // sum of dot product of targetBasket and priceError
146
- const totalPortion = targetBasket
147
- .map((portion: Decimal, i: number) => portion.mul(priceError[i]))
148
- .reduce((a: Decimal, b: Decimal) => a.add(b))
149
-
150
- if (totalPortion.gte(ONE)) {
151
- throw new Error('totalPortion > 1')
152
- }
153
-
154
- // D18{BU/share} = {1} * D18 * {BU/share}
155
- newLimits.low = bn(ONE.div(ONE.add(totalPortion)).mul(D18d))
156
- newLimits.high = bn(ONE.add(totalPortion).mul(D18d))
157
- }
158
-
159
- if (logging) {
160
- console.log("newWeights", newWeights)
161
- console.log('newPrices', newPrices)
162
- console.log('newLimits', newLimits)
163
- }
164
-
165
- return {
166
- weights: newWeights,
167
- prices: newPrices,
168
- limits: newLimits,
169
- }
170
- }
package/src/types.ts DELETED
@@ -1,35 +0,0 @@
1
- export enum PriceControl {
2
- NONE = 0,
3
- PARTIAL = 1,
4
- ATOMIC_SWAP = 2,
5
- }
6
-
7
- export interface RebalanceLimits {
8
- low: bigint // D18{BU/share}
9
- spot: bigint // D18{BU/share}
10
- high: bigint // D18{BU/share}
11
- }
12
-
13
- export interface WeightRange {
14
- low: bigint // D27{tok/BU}
15
- spot: bigint // D27{tok/BU}
16
- high: bigint // D27{tok/BU}
17
- }
18
-
19
- export interface PriceRange {
20
- low: bigint // D27{USD/tok}
21
- high: bigint // D27{USD/tok}
22
- }
23
-
24
- export interface Rebalance {
25
- nonce: bigint
26
- tokens: string[]
27
- weights: WeightRange[]
28
- initialPrices: PriceRange[]
29
- inRebalance: boolean[]
30
- limits: RebalanceLimits
31
- startedAt: bigint
32
- restrictedUntil: bigint
33
- availableUntil: bigint
34
- priceControl: PriceControl
35
- }
package/src/utils.ts DELETED
@@ -1,36 +0,0 @@
1
- import Decimal from 'decimal.js-light'
2
-
3
- import { bn, D18d } from './numbers'
4
-
5
- /**
6
- * This function can be used to get a basket distribution EITHER from a set of historical basket weights
7
- * or from a set of current balances. Make sure to use prices from the right time.
8
- *
9
- * @param _bals {tok} Current balances; or previous historical weights
10
- * @param _prices {USD/wholeTok} USD prices for each *whole* token; or previous historical prices
11
- * @param decimals Decimals of each token
12
- * @returns D18{1} Current basket, total will be around 1e18 but not exactly
13
- */
14
- export const getBasketDistribution = (
15
- _bals: bigint[],
16
- _prices: number[],
17
- decimals: bigint[]
18
- ): bigint[] => {
19
- const decimalScale = decimals.map((d) => new Decimal(`1e${d}`))
20
-
21
- // {wholeTok} = {tok} / {tok/wholeTok}
22
- const bals = _bals.map((bal, i) =>
23
- new Decimal(bal.toString()).div(decimalScale[i])
24
- )
25
-
26
- // {USD/wholeTok} = {USD/wholeTok}
27
- const prices = _prices.map((a) => new Decimal(a.toString()))
28
-
29
- // {USD} = {wholeTok} * {USD/wholeTok}
30
- const totalValue = bals
31
- .map((bal, i) => bal.mul(prices[i]))
32
- .reduce((a, b) => a.add(b))
33
-
34
- // D18{1} = {wholeTok} * {USD/wholeTok} / {USD}
35
- return bals.map((bal, i) => bn(bal.mul(prices[i]).div(totalValue).mul(D18d)))
36
- }