@reserve-protocol/dtf-rebalance-lib 2.6.3 → 3.0.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.
package/dist/numbers.d.ts CHANGED
@@ -3,6 +3,7 @@ import type { Decimal as DecimalType } from "decimal.js-light";
3
3
  export declare const D27n: bigint;
4
4
  export declare const D18n: bigint;
5
5
  export declare const D9n: bigint;
6
+ export declare const D256_MAXn: bigint;
6
7
  export declare const D27d: DecimalType;
7
8
  export declare const D18d: DecimalType;
8
9
  export declare const D9d: DecimalType;
package/dist/numbers.js CHANGED
@@ -3,13 +3,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.bn = exports.TWO = exports.ONE = exports.EPSILON = exports.ZERO = exports.D9d = exports.D18d = exports.D27d = exports.D9n = exports.D18n = exports.D27n = void 0;
6
+ exports.bn = exports.TWO = exports.ONE = exports.EPSILON = exports.ZERO = exports.D9d = exports.D18d = exports.D27d = exports.D256_MAXn = exports.D9n = exports.D18n = exports.D27n = void 0;
7
7
  const decimal_js_light_1 = __importDefault(require("decimal.js-light"));
8
8
  // Create a local Decimal constructor with custom precision
9
9
  const Decimal = decimal_js_light_1.default.clone({ precision: 100 });
10
10
  exports.D27n = 10n ** 27n;
11
11
  exports.D18n = 10n ** 18n;
12
12
  exports.D9n = 10n ** 9n;
13
+ exports.D256_MAXn = 10n ** 256n - 1n;
13
14
  exports.D27d = new Decimal("1e27");
14
15
  exports.D18d = new Decimal("1e18");
15
16
  exports.D9d = new Decimal("1e9");
@@ -99,20 +99,22 @@ const getOpenAuction = (rebalance, _supply, _initialSupply, _initialAssets = [],
99
99
  // {wholeTok/wholeShare} = {tok} / {tok/wholeTok} / {wholeShare}
100
100
  const folio = _assets.map((bal, i) => new utils_1.Decimal(bal.toString()).div(decimalScale[i]).div(supply));
101
101
  // {wholeTok/wholeBU} = D27{tok/BU} * {BU/wholeBU} / {tok/wholeTok} / D27
102
- let weightRanges = rebalance.weights.map((range, i) => {
102
+ let weightRanges = rebalance.tokens.map((params, i) => {
103
103
  return {
104
- low: new utils_1.Decimal(range.low.toString()).div(decimalScale[i]).div(numbers_1.D9d),
105
- spot: new utils_1.Decimal(range.spot.toString()).div(decimalScale[i]).div(numbers_1.D9d),
106
- high: new utils_1.Decimal(range.high.toString()).div(decimalScale[i]).div(numbers_1.D9d),
104
+ low: new utils_1.Decimal(params.weight.low.toString()).div(decimalScale[i]).div(numbers_1.D9d),
105
+ spot: new utils_1.Decimal(params.weight.spot.toString()).div(decimalScale[i]).div(numbers_1.D9d),
106
+ high: new utils_1.Decimal(params.weight.high.toString()).div(decimalScale[i]).div(numbers_1.D9d),
107
107
  };
108
108
  });
109
+ // {wholeTok} = {tok} / {tok/wholeTok}
110
+ const maxAuctionSizes = rebalance.tokens.map((params, i) => new utils_1.Decimal(params.maxAuctionSize.toString()).div(decimalScale[i]));
109
111
  const finalStageAt = new utils_1.Decimal(_finalStageAt.toString());
110
112
  // ================================================================
111
113
  // calculate ideal spot limit, the actual BU<->share ratio
112
114
  // {USD/wholeShare} = {wholeTok/wholeShare} * {USD/wholeTok}
113
115
  const shareValue = folio
114
116
  .map((f, i) => {
115
- if (!rebalance.inRebalance[i]) {
117
+ if (!rebalance.tokens[i].inRebalance) {
116
118
  return numbers_1.ZERO;
117
119
  }
118
120
  return f.mul(prices[i]);
@@ -121,7 +123,7 @@ const getOpenAuction = (rebalance, _supply, _initialSupply, _initialAssets = [],
121
123
  // {USD/wholeBU} = {wholeTok/wholeBU} * {USD/wholeTok}
122
124
  const buValue = weightRanges
123
125
  .map((weightRange, i) => {
124
- if (!rebalance.inRebalance[i]) {
126
+ if (!rebalance.tokens[i].inRebalance) {
125
127
  return numbers_1.ZERO;
126
128
  }
127
129
  return weightRange.spot.mul(prices[i]);
@@ -139,8 +141,8 @@ const getOpenAuction = (rebalance, _supply, _initialSupply, _initialAssets = [],
139
141
  // ================================================================
140
142
  // calculate portionBeingEjected
141
143
  const ejectionIndices = [];
142
- for (let i = 0; i < rebalance.weights.length; i++) {
143
- if (rebalance.inRebalance[i] && rebalance.weights[i].spot == 0n) {
144
+ for (let i = 0; i < rebalance.tokens.length; i++) {
145
+ if (rebalance.tokens[i].inRebalance && rebalance.tokens[i].weight.spot == 0n) {
144
146
  ejectionIndices.push(i);
145
147
  }
146
148
  }
@@ -161,7 +163,7 @@ const getOpenAuction = (rebalance, _supply, _initialSupply, _initialAssets = [],
161
163
  // {1} = {USD/wholeShare} / {USD/wholeShare}
162
164
  const progression = folio
163
165
  .map((actualBalance, i) => {
164
- if (!rebalance.inRebalance[i]) {
166
+ if (!rebalance.tokens[i].inRebalance) {
165
167
  return numbers_1.ZERO;
166
168
  }
167
169
  // {wholeTok/wholeShare}
@@ -175,7 +177,7 @@ const getOpenAuction = (rebalance, _supply, _initialSupply, _initialAssets = [],
175
177
  // {1} = {USD/wholeShare} / {USD/wholeShare}
176
178
  let initialProgression = initialFolio
177
179
  .map((initialBalance, i) => {
178
- if (!rebalance.inRebalance[i]) {
180
+ if (!rebalance.tokens[i].inRebalance) {
179
181
  return numbers_1.ZERO;
180
182
  }
181
183
  // {wholeTok/wholeShare}
@@ -270,7 +272,7 @@ const getOpenAuction = (rebalance, _supply, _initialSupply, _initialAssets = [],
270
272
  high: new utils_1.Decimal(newLimits.high.toString()).div(numbers_1.D18d),
271
273
  };
272
274
  // D27{tok/BU}
273
- const newWeights = rebalance.weights.map((weightRange, i) => {
275
+ const newWeights = rebalance.tokens.map((params, i) => {
274
276
  // {wholeTok/wholeBU} = {USD/wholeShare} * {1} / {wholeBU/wholeShare} / {USD/wholeTok}
275
277
  const idealWeight = shareValue.mul(targetBasket[i]).div(actualLimits.spot).div(prices[i]);
276
278
  // D27{tok/BU} = {wholeTok/wholeBU} * D27 * {tok/wholeTok} / {BU/wholeBU}
@@ -283,7 +285,7 @@ const getOpenAuction = (rebalance, _supply, _initialSupply, _initialAssets = [],
283
285
  high:
284
286
  // hold surpluses aside if ejecting
285
287
  round == AuctionRound.EJECT
286
- ? weightRange.high
288
+ ? params.weight.high
287
289
  : (0, numbers_1.bn)(idealWeight
288
290
  .mul(idealHighLimit.div(actualLimits.high)) // add the portion of `delta` we failed to propagate through to the high limit
289
291
  .mul(numbers_1.D9d)
@@ -297,23 +299,23 @@ const getOpenAuction = (rebalance, _supply, _initialSupply, _initialAssets = [],
297
299
  newWeightsD27.high = newWeightsD27.spot;
298
300
  }
299
301
  // enforce in range
300
- if (newWeightsD27.low < weightRange.low) {
301
- newWeightsD27.low = weightRange.low;
302
+ if (newWeightsD27.low < params.weight.low) {
303
+ newWeightsD27.low = params.weight.low;
302
304
  }
303
- else if (newWeightsD27.low > weightRange.high) {
304
- newWeightsD27.low = weightRange.high;
305
+ else if (newWeightsD27.low > params.weight.high) {
306
+ newWeightsD27.low = params.weight.high;
305
307
  }
306
- if (newWeightsD27.spot < weightRange.low) {
307
- newWeightsD27.spot = weightRange.low;
308
+ if (newWeightsD27.spot < params.weight.low) {
309
+ newWeightsD27.spot = params.weight.low;
308
310
  }
309
- else if (newWeightsD27.spot > weightRange.high) {
310
- newWeightsD27.spot = weightRange.high;
311
+ else if (newWeightsD27.spot > params.weight.high) {
312
+ newWeightsD27.spot = params.weight.high;
311
313
  }
312
- if (newWeightsD27.high < weightRange.low) {
313
- newWeightsD27.high = weightRange.low;
314
+ if (newWeightsD27.high < params.weight.low) {
315
+ newWeightsD27.high = params.weight.low;
314
316
  }
315
- else if (newWeightsD27.high > weightRange.high) {
316
- newWeightsD27.high = weightRange.high;
317
+ else if (newWeightsD27.high > params.weight.high) {
318
+ newWeightsD27.high = params.weight.high;
317
319
  }
318
320
  return newWeightsD27;
319
321
  });
@@ -323,14 +325,14 @@ const getOpenAuction = (rebalance, _supply, _initialSupply, _initialAssets = [],
323
325
  // ================================================================
324
326
  // get new prices, constrained by extremes
325
327
  // D27{nanoUSD/tok}
326
- const newPrices = rebalance.initialPrices.map((initialPrice, i) => {
328
+ const newPrices = rebalance.tokens.map((params, i) => {
327
329
  // D27{nanoUSD/tok} = {USD/wholeTok} * {nanoUSD/USD} * D27 / {tok/wholeTok}
328
330
  const spotPrice = (0, numbers_1.bn)(prices[i].mul(numbers_1.D9d).mul(numbers_1.D27d).div(decimalScale[i]));
329
- if (spotPrice < initialPrice.low || spotPrice > initialPrice.high) {
330
- throw new Error(`Token ${rebalance.tokens[i]}: spot price ${spotPrice.toString()} out of bounds relative to initial range [${initialPrice.low.toString()}, ${initialPrice.high.toString()}]! auction launcher MUST closeRebalance to prevent loss!`);
331
+ if (spotPrice < params.price.low || spotPrice > params.price.high) {
332
+ throw new Error(`Token ${rebalance.tokens[i]}: spot price ${spotPrice.toString()} out of bounds relative to initial range [${params.price.low.toString()}, ${params.price.high.toString()}]! auction launcher MUST closeRebalance to prevent loss!`);
331
333
  }
332
334
  if (rebalance.priceControl == types_1.PriceControl.NONE) {
333
- return initialPrice;
335
+ return params.price;
334
336
  }
335
337
  // D27{nanoUSD/tok} = {USD/wholeTok} * {nanoUSD/USD} * D27 / {tok/wholeTok}
336
338
  const pricesD27 = {
@@ -338,18 +340,18 @@ const getOpenAuction = (rebalance, _supply, _initialSupply, _initialAssets = [],
338
340
  high: (0, numbers_1.bn)(prices[i].div(numbers_1.ONE.sub(priceError[i])).mul(numbers_1.D9d).mul(numbers_1.D27d).div(decimalScale[i])),
339
341
  };
340
342
  // low
341
- if (pricesD27.low < initialPrice.low) {
342
- pricesD27.low = initialPrice.low;
343
+ if (pricesD27.low < params.price.low) {
344
+ pricesD27.low = params.price.low;
343
345
  }
344
- if (pricesD27.low > initialPrice.high) {
345
- pricesD27.low = initialPrice.high;
346
+ if (pricesD27.low > params.price.high) {
347
+ pricesD27.low = params.price.high;
346
348
  }
347
349
  // high
348
- if (pricesD27.high < initialPrice.low) {
349
- pricesD27.high = initialPrice.low;
350
+ if (pricesD27.high < params.price.low) {
351
+ pricesD27.high = params.price.low;
350
352
  }
351
- if (pricesD27.high > initialPrice.high) {
352
- pricesD27.high = initialPrice.high;
353
+ if (pricesD27.high > params.price.high) {
354
+ pricesD27.high = params.price.high;
353
355
  }
354
356
  if (pricesD27.low == pricesD27.high && priceError[i].gt(numbers_1.ZERO)) {
355
357
  throw new Error("no price range");
@@ -370,11 +372,11 @@ const getOpenAuction = (rebalance, _supply, _initialSupply, _initialAssets = [],
370
372
  const surplusTokenSizes = []; // {USD}
371
373
  const deficitTokens = [];
372
374
  const deficitTokenSizes = []; // {USD}
373
- rebalance.tokens.forEach((token, i) => {
374
- if (!rebalance.inRebalance[i]) {
375
+ rebalance.tokens.forEach((params, i) => {
376
+ if (!rebalance.tokens[i].inRebalance) {
375
377
  return;
376
378
  }
377
- auctionTokens.push(token);
379
+ auctionTokens.push(params.token);
378
380
  auctionWeights.push(newWeights[i]);
379
381
  auctionPrices.push(newPrices[i]);
380
382
  // {tok} = D27{tok/BU} * D18{BU/share} * {share} / D18 / D27
@@ -382,21 +384,27 @@ const getOpenAuction = (rebalance, _supply, _initialSupply, _initialAssets = [],
382
384
  const sellDownTo = (newWeights[i].high * newLimits.high * _supply + (numbers_1.D18n * numbers_1.D27n - 1n)) / numbers_1.D18n / numbers_1.D27n;
383
385
  if (_assets[i] < buyUpTo) {
384
386
  // {wholeTok} = {tok} / {tok/wholeTok}
385
- const deficitAmount = new utils_1.Decimal((buyUpTo - _assets[i]).toString()).div(decimalScale[i]);
387
+ let deficitAmount = new utils_1.Decimal((buyUpTo - _assets[i]).toString()).div(decimalScale[i]);
388
+ if (deficitAmount.gt(maxAuctionSizes[i])) {
389
+ deficitAmount = maxAuctionSizes[i];
390
+ }
386
391
  // {USD} = {wholeTok} * {USD/wholeTok}
387
392
  const tokenDeficitValue = deficitAmount.mul(prices[i]);
388
393
  if (tokenDeficitValue.gt(numbers_1.EPSILON)) {
389
- deficitTokens.push(token);
394
+ deficitTokens.push(params.token);
390
395
  deficitTokenSizes.push(tokenDeficitValue.toNumber());
391
396
  }
392
397
  }
393
398
  else if (_assets[i] > sellDownTo) {
394
399
  // {wholeTok} = {tok} / {tok/wholeTok}
395
- const surplusAmount = new utils_1.Decimal((_assets[i] - sellDownTo).toString()).div(decimalScale[i]);
400
+ let surplusAmount = new utils_1.Decimal((_assets[i] - sellDownTo).toString()).div(decimalScale[i]);
401
+ if (surplusAmount.gt(maxAuctionSizes[i])) {
402
+ surplusAmount = maxAuctionSizes[i];
403
+ }
396
404
  // {USD} = {wholeTok} * {USD/wholeTok}
397
405
  const tokenSurplusValue = surplusAmount.mul(prices[i]);
398
406
  if (tokenSurplusValue.gt(numbers_1.EPSILON)) {
399
- surplusTokens.push(token);
407
+ surplusTokens.push(params.token);
400
408
  surplusTokenSizes.push(tokenSurplusValue.toNumber());
401
409
  }
402
410
  }
@@ -1,7 +1,6 @@
1
- import { PriceRange, RebalanceLimits, WeightRange } from "./types";
1
+ import { RebalanceLimits, TokenRebalanceParams } from "./types";
2
2
  export interface StartRebalanceArgsPartial {
3
- weights: WeightRange[];
4
- prices: PriceRange[];
3
+ tokens: TokenRebalanceParams[];
5
4
  limits: RebalanceLimits;
6
5
  }
7
6
  /**
@@ -17,7 +16,8 @@ export interface StartRebalanceArgsPartial {
17
16
  * @param _prices {USD/wholeTok} USD prices for each *whole* token
18
17
  * @param _priceError {1} Price error per token to use in the rebalance; should be larger than price error during openAuction
19
18
  * @param _dtfPrice {USD/wholeShare} DTF price
19
+ * @param _maxAuctionSize {USD} The maximum auction size for each token
20
20
  * @param weightControl TRACKING=false, NATIVE=true
21
21
  * @param deferWeights Whether to use the full range for weights, only possible for NATIVE DTFs
22
22
  */
23
- export declare const getStartRebalance: (_supply: bigint, tokens: string[], _assets: bigint[], decimals: bigint[], _targetBasket: bigint[], _prices: number[], _priceError: number[], weightControl: boolean, deferWeights: boolean, debug?: boolean) => StartRebalanceArgsPartial;
23
+ export declare const getStartRebalance: (_supply: bigint, tokens: string[], _assets: bigint[], decimals: bigint[], _targetBasket: bigint[], _prices: number[], _priceError: number[], _maxAuctionSizes: number[], weightControl: boolean, deferWeights: boolean, debug?: boolean) => StartRebalanceArgsPartial;
@@ -16,12 +16,13 @@ const numbers_1 = require("./numbers");
16
16
  * @param _prices {USD/wholeTok} USD prices for each *whole* token
17
17
  * @param _priceError {1} Price error per token to use in the rebalance; should be larger than price error during openAuction
18
18
  * @param _dtfPrice {USD/wholeShare} DTF price
19
+ * @param _maxAuctionSize {USD} The maximum auction size for each token
19
20
  * @param weightControl TRACKING=false, NATIVE=true
20
21
  * @param deferWeights Whether to use the full range for weights, only possible for NATIVE DTFs
21
22
  */
22
- const getStartRebalance = (_supply, tokens, _assets, decimals, _targetBasket, _prices, _priceError, weightControl, deferWeights, debug) => {
23
+ const getStartRebalance = (_supply, tokens, _assets, decimals, _targetBasket, _prices, _priceError, _maxAuctionSizes, weightControl, deferWeights, debug) => {
23
24
  if (debug) {
24
- console.log("getStartRebalance", _supply, tokens, _assets, decimals, _targetBasket, _prices, _priceError, weightControl, deferWeights);
25
+ console.log("getStartRebalance", _supply, tokens, _assets, decimals, _targetBasket, _prices, _priceError, _maxAuctionSizes, weightControl, deferWeights);
25
26
  }
26
27
  if (deferWeights && !weightControl) {
27
28
  throw new Error("deferWeights is not supported for tracking DTFs");
@@ -45,9 +46,11 @@ const getStartRebalance = (_supply, tokens, _assets, decimals, _targetBasket, _p
45
46
  // ================================================================
46
47
  const newWeights = [];
47
48
  const newPrices = [];
49
+ const maxAuctionSizes = [];
50
+ const maxPriceError = new utils_1.Decimal("0.9");
48
51
  for (let i = 0; i < tokens.length; i++) {
49
- if (priceError[i].gte(numbers_1.ONE)) {
50
- throw new Error("price error >= 1");
52
+ if (priceError[i].gt(maxPriceError)) {
53
+ throw new Error("price error > 0.9");
51
54
  }
52
55
  // === newWeights ===
53
56
  // {USD} = {wholeTok} * {USD/wholeTok}
@@ -92,22 +95,35 @@ const getStartRebalance = (_supply, tokens, _assets, decimals, _targetBasket, _p
92
95
  const priceMultiplier = numbers_1.D27d.div(new utils_1.Decimal(`1e${decimals[i]}`));
93
96
  // D27{nanoUSD/tok} = {USD/wholeTok} * {1} * D27{wholeTok/tok} * {nanoUSD/USD}
94
97
  const low = (0, numbers_1.bn)(prices[i].mul(numbers_1.ONE.sub(priceError[i])).mul(priceMultiplier).mul(numbers_1.D9d));
95
- let high = (0, numbers_1.bn)(prices[i].div(numbers_1.ONE.sub(priceError[i])).mul(priceMultiplier).mul(numbers_1.D9d));
98
+ let high = (0, numbers_1.bn)(prices[i].div(numbers_1.ONE.sub(priceError[i])).mul(priceMultiplier).mul(numbers_1.D9d)) + 1n;
96
99
  // check if prices are valid
97
100
  if (low < 0n || low > high || high > numbers_1.D18n * numbers_1.D27n) {
98
101
  throw new Error(`invalid prices for token ${tokens[i]}: low: ${low}, high: ${high}`);
99
102
  }
100
- // constrain price range if too large, but not by more than 10%
101
- if (high > low * 110n) {
102
- throw new Error(`invalid price range for token ${tokens[i]}: low: ${low}, high: ${high}`);
103
- }
103
+ // due to floor rounding `low`, `high` can be slightly more than 100x even at 0.9 price error
104
104
  if (high > low * 100n) {
105
+ // keep consistent geometric mean
106
+ if (high > low * 100n + 100n) {
107
+ throw new Error("something has gone very wrong");
108
+ }
105
109
  high = low * 100n;
106
110
  }
107
111
  newPrices.push({
108
112
  low: low,
109
113
  high: high,
110
114
  });
115
+ // === maxAuctionSizes ===
116
+ // {USD}
117
+ const maxAuctionSize = new utils_1.Decimal(_maxAuctionSizes[i].toString());
118
+ // {tok} = {USD} * {tok/wholeTok} / {USD/wholeTok}
119
+ let maxAuctionSizeTok = (0, numbers_1.bn)(maxAuctionSize.mul(new utils_1.Decimal(`1e${decimals[i]}`)).div(prices[i]));
120
+ if (maxAuctionSizeTok == 0n) {
121
+ throw new Error(`maxAuctionSize for token ${tokens[i]} is 0`);
122
+ }
123
+ if (maxAuctionSizeTok > numbers_1.D256_MAXn) {
124
+ maxAuctionSizeTok = numbers_1.D256_MAXn;
125
+ }
126
+ maxAuctionSizes.push(maxAuctionSizeTok);
111
127
  }
112
128
  // ================================================================
113
129
  // newLimits
@@ -130,8 +146,13 @@ const getStartRebalance = (_supply, tokens, _assets, decimals, _targetBasket, _p
130
146
  console.log("newLimits", newLimits);
131
147
  }
132
148
  return {
133
- weights: newWeights,
134
- prices: newPrices,
149
+ tokens: tokens.map((token, i) => ({
150
+ token: token,
151
+ weight: newWeights[i],
152
+ price: newPrices[i],
153
+ maxAuctionSize: maxAuctionSizes[i],
154
+ inRebalance: true,
155
+ })),
135
156
  limits: newLimits,
136
157
  };
137
158
  };
package/dist/types.d.ts CHANGED
@@ -17,17 +17,24 @@ export interface PriceRange {
17
17
  low: bigint;
18
18
  high: bigint;
19
19
  }
20
- export interface Rebalance {
21
- nonce: bigint;
22
- tokens: string[];
23
- weights: WeightRange[];
24
- initialPrices: PriceRange[];
25
- inRebalance: boolean[];
26
- limits: RebalanceLimits;
20
+ export interface TokenRebalanceParams {
21
+ token: string;
22
+ weight: WeightRange;
23
+ price: PriceRange;
24
+ maxAuctionSize: bigint;
25
+ inRebalance: boolean;
26
+ }
27
+ export interface RebalanceTimestamps {
27
28
  startedAt: bigint;
28
29
  restrictedUntil: bigint;
29
30
  availableUntil: bigint;
31
+ }
32
+ export interface Rebalance {
33
+ nonce: bigint;
30
34
  priceControl: PriceControl;
35
+ tokens: TokenRebalanceParams[];
36
+ limits: RebalanceLimits;
37
+ timestamps: RebalanceTimestamps;
31
38
  }
32
39
  export interface Folio {
33
40
  name: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reserve-protocol/dtf-rebalance-lib",
3
- "version": "2.6.3",
3
+ "version": "3.0.0",
4
4
  "description": "Rebalancing library for DTFs in typescript",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",