@reserve-protocol/dtf-rebalance-lib 0.0.1

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.
@@ -0,0 +1,15 @@
1
+ on:
2
+ push:
3
+ branches: main
4
+
5
+ jobs:
6
+ publish:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v4
10
+ - uses: actions/setup-node@v3
11
+ with:
12
+ node-version: "20"
13
+ - uses: JS-DevTools/npm-publish@v3
14
+ with:
15
+ token: ${{ secrets.NPM_TOKEN }}
package/LICENSE.md ADDED
@@ -0,0 +1,55 @@
1
+ # Blue Oak Model License
2
+
3
+ Version 1.0.0
4
+
5
+ ## Purpose
6
+
7
+ This license gives everyone as much permission to work with
8
+ this software as possible, while protecting contributors
9
+ from liability.
10
+
11
+ ## Acceptance
12
+
13
+ In order to receive this license, you must agree to its
14
+ rules. The rules of this license are both obligations
15
+ under that agreement and conditions to your license.
16
+ You must not do anything with this software that triggers
17
+ a rule that you cannot or will not follow.
18
+
19
+ ## Copyright
20
+
21
+ Each contributor licenses you to do everything with this
22
+ software that would otherwise infringe that contributor's
23
+ copyright in it.
24
+
25
+ ## Notices
26
+
27
+ You must ensure that everyone who gets a copy of
28
+ any part of this software from you, with or without
29
+ changes, also gets the text of this license or a link to
30
+ <https://blueoakcouncil.org/license/1.0.0>.
31
+
32
+ ## Excuse
33
+
34
+ If anyone notifies you in writing that you have not
35
+ complied with [Notices](#notices), you can keep your
36
+ license by taking all practical steps to comply within 30
37
+ days after the notice. If you do not do so, your license
38
+ ends immediately.
39
+
40
+ ## Patent
41
+
42
+ Each contributor licenses you to do everything with this
43
+ software that would otherwise infringe any patent claims
44
+ they can license or become able to license.
45
+
46
+ ## Reliability
47
+
48
+ No contributor can revoke this license.
49
+
50
+ ## No Liability
51
+
52
+ **_As far as the law allows, this software comes as is,
53
+ without any warranty or condition, and no contributor
54
+ will be liable to anyone for any damages related to this
55
+ software or this license, under any kind of legal claim._**
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # dtf-rebalance-lib
2
+
3
+ Rebalancing library for DTFs in typescript.
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@reserve-protocol/dtf-rebalance-lib",
3
+ "version": "0.0.1",
4
+ "description": "Rebalancing library for DTFs in typescript",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "prepublishOnly": "npm run build",
11
+ "test": "tsc --project tsconfig.test.json && node --test dist/test/*.test.js"
12
+ },
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "keywords": [
17
+ "typescript",
18
+ "npm",
19
+ "package"
20
+ ],
21
+ "author": "Reserve Team",
22
+ "license": "BlueOak-1.0.0",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/reserve-protocol/dtf-rebalance-lib.git"
26
+ },
27
+ "bugs": {
28
+ "url": "https://github.com/reserve-protocol/dtf-rebalance-lib/issues"
29
+ },
30
+ "homepage": "https://github.com/reserve-protocol/dtf-rebalance-lib#readme",
31
+ "peerDependencies": {
32
+ "decimal.js-light": "^2.5.1"
33
+ },
34
+ "devDependencies": {
35
+ "typescript": "^5.8.3"
36
+ }
37
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
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 ADDED
@@ -0,0 +1,17 @@
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
+ }
@@ -0,0 +1,495 @@
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
+ 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 = 0.9
114
+ ): [OpenAuctionArgs, AuctionMetrics] => {
115
+
116
+ if (
117
+ rebalance.tokens.length != _targetBasket.length ||
118
+ _targetBasket.length != _folio.length ||
119
+ _folio.length != _decimals.length ||
120
+ _decimals.length != _prices.length ||
121
+ _prices.length != _priceError.length
122
+ ) {
123
+ throw new Error('length mismatch')
124
+ }
125
+
126
+ if (_finalStageAt >= 1) {
127
+ throw new Error('finalStageAt must be less than 1')
128
+ }
129
+
130
+ // ================================================================
131
+
132
+ // {wholeShare} = {share} / {share/wholeShare}
133
+ const supply = new Decimal(_supply.toString()).div(D18d)
134
+
135
+ // {1} = D18{1} / D18
136
+ const targetBasket = _targetBasket.map((a) =>
137
+ new Decimal(a.toString()).div(D18d)
138
+ )
139
+
140
+ // {USD/wholeTok}
141
+ const prices = _prices.map((a) => new Decimal(a))
142
+ for (let i = 0; i < prices.length; i++) {
143
+ if (prices[i].eq(ZERO)) {
144
+ throw new Error(`missing price for token ${rebalance.tokens[i]}`)
145
+ }
146
+ }
147
+
148
+ // {1}
149
+ const priceError = _priceError.map((a) => new Decimal(a.toString()))
150
+
151
+ // {tok/wholeTok}
152
+ const decimalScale = _decimals.map((a) => new Decimal(`1e${a}`))
153
+
154
+ // {wholeTok/wholeShare} = D18{tok/share} * {share/wholeShare} / {tok/wholeTok} / D18
155
+ const initialFolio = _initialFolio.map((c: bigint, i: number) =>
156
+ new Decimal(c.toString()).div(decimalScale[i])
157
+ )
158
+
159
+ // {wholeTok/wholeShare} = D18{tok/share} * {share/wholeShare} / {tok/wholeTok} / D18
160
+ const folio = _folio.map((c: bigint, i: number) =>
161
+ new Decimal(c.toString()).div(decimalScale[i])
162
+ )
163
+
164
+ // {wholeTok/wholeBU} = D27{tok/BU} * {BU/wholeBU} / {tok/wholeTok} / D27
165
+ let weightRanges = rebalance.weights.map((range: WeightRange, i: number) => {
166
+ return {
167
+ low: new Decimal(range.low.toString())
168
+ .mul(D18d)
169
+ .div(decimalScale[i])
170
+ .div(D27d),
171
+ spot: new Decimal(range.spot.toString())
172
+ .mul(D18d)
173
+ .div(decimalScale[i])
174
+ .div(D27d),
175
+ high: new Decimal(range.high.toString())
176
+ .mul(D18d)
177
+ .div(decimalScale[i])
178
+ .div(D27d),
179
+ }
180
+ })
181
+
182
+ const finalStageAt = new Decimal(_finalStageAt.toString())
183
+
184
+
185
+ // ================================================================
186
+
187
+ // calculate ideal spot limit, the actual BU<->share ratio
188
+
189
+ // {USD/wholeShare} = {wholeTok/wholeShare} * {USD/wholeTok}
190
+ const shareValue = folio
191
+ .map((f: Decimal, i: number) => f.mul(prices[i]))
192
+ .reduce((a, b) => a.add(b))
193
+
194
+ // {USD/wholeBU} = {wholeTok/wholeBU} * {USD/wholeTok}
195
+ const buValue = weightRanges
196
+ .map((weightRange, i) => weightRange.spot.mul(prices[i]))
197
+ .reduce((a, b) => a.add(b))
198
+
199
+
200
+ // ================================================================
201
+
202
+ // calculate rebalanceTarget
203
+
204
+ const ejectionIndices: number[] = []
205
+ for (let i = 0; i < rebalance.weights.length; i++) {
206
+ if (rebalance.weights[i].spot == 0n) {
207
+ ejectionIndices.push(i)
208
+ }
209
+ }
210
+
211
+ // {1} = {wholeTok/wholeShare} * {USD/wholeTok} / {USD/wholeShare}
212
+ const portionBeingEjected = ejectionIndices
213
+ .map((i) => folio[i].mul(prices[i]))
214
+ .reduce((a, b) => a.add(b), ZERO)
215
+ .div(shareValue)
216
+
217
+
218
+ // {1} = {USD/wholeShare} / {USD/wholeShare}
219
+ let progression = folio
220
+ .map((actualBalance, i) => {
221
+ // {wholeTok/wholeShare} = {USD/wholeShare} * {1} / {USD/wholeTok}
222
+ const balanceExpected = shareValue.mul(targetBasket[i]).div(prices[i])
223
+
224
+ // {wholeTok/wholeShare} = {wholeTok/wholeBU} * {wholeBU/wholeShare}
225
+ const balanceInBU = balanceExpected.gt(actualBalance)
226
+ ? actualBalance
227
+ : balanceExpected
228
+
229
+ // {USD/wholeShare} = {wholeTok/wholeShare} * {USD/wholeTok}
230
+ return balanceInBU.mul(prices[i])
231
+ })
232
+ .reduce((a, b) => a.add(b))
233
+ .div(shareValue)
234
+
235
+ // {1} = {USD/wholeShare} / {USD/wholeShare}
236
+ const initialProgression = initialFolio
237
+ .map((initialBalance, i) => {
238
+ // {wholeTok/wholeShare} = {USD/wholeShare} * {1} / {USD/wholeTok}
239
+ const balanceExpected = shareValue.mul(targetBasket[i]).div(prices[i])
240
+
241
+ // {wholeTok/wholeShare} = {wholeTok/wholeBU} * {wholeBU/wholeShare}
242
+ const balanceInBU = balanceExpected.gt(initialBalance)
243
+ ? initialBalance
244
+ : balanceExpected
245
+
246
+ // {USD/wholeShare} = {wholeTok/wholeShare} * {USD/wholeTok}
247
+ return balanceInBU.mul(prices[i])
248
+ })
249
+ .reduce((a, b) => a.add(b))
250
+ .div(shareValue)
251
+
252
+ if (progression < initialProgression) {
253
+ progression = initialProgression // don't go backwards
254
+ }
255
+
256
+
257
+ // {1} = {1} / {1}
258
+ const relativeProgression = initialProgression.eq(ONE)
259
+ ? ONE
260
+ : progression.sub(initialProgression).div(ONE.sub(initialProgression))
261
+
262
+ let rebalanceTarget = ONE
263
+ let round: AuctionRound = AuctionRound.FINAL
264
+
265
+ // make it an eject auction if there is 1 bps or more of value to eject
266
+ if (portionBeingEjected.gte(1e-4)) {
267
+ round = AuctionRound.EJECT
268
+
269
+ rebalanceTarget = progression.add(portionBeingEjected.mul(1.5)) // set rebalanceTarget to 50% more than needed, to ensure ejection completes
270
+ if (rebalanceTarget.gt(ONE)) {
271
+ rebalanceTarget = ONE
272
+ }
273
+ } else if (relativeProgression.lt(finalStageAt.sub(0.02))) {
274
+ // wiggle room to prevent having to re-run an auction at the same stage after price movement
275
+ round = AuctionRound.PROGRESS
276
+
277
+ rebalanceTarget = finalStageAt
278
+ }
279
+
280
+ // {1}
281
+ const delta = ONE.sub(rebalanceTarget)
282
+
283
+ // ================================================================
284
+
285
+ // get new limits, constrained by extremes
286
+
287
+ // {wholeBU/wholeShare} = {USD/wholeShare} / {USD/wholeBU}
288
+ const spotLimit = shareValue.div(buValue)
289
+
290
+
291
+ // D18{BU/share} = {wholeBU/wholeShare} * D18 * {1}
292
+ const newLimits = {
293
+ low: bn(spotLimit.sub(spotLimit.mul(delta)).mul(D18d)),
294
+ spot: bn(spotLimit.mul(D18d)),
295
+ high: bn(spotLimit.add(spotLimit.mul(delta)).mul(D18d)),
296
+ }
297
+
298
+ // low
299
+ if (newLimits.low < rebalance.limits.low) {
300
+ newLimits.low = rebalance.limits.low
301
+ }
302
+ if (newLimits.low > rebalance.limits.high) {
303
+ newLimits.low = rebalance.limits.high
304
+ }
305
+
306
+ // spot
307
+ if (newLimits.spot < rebalance.limits.low) {
308
+ newLimits.spot = rebalance.limits.low
309
+ }
310
+ if (newLimits.spot > rebalance.limits.high) {
311
+ newLimits.spot = rebalance.limits.high
312
+ }
313
+
314
+ // high
315
+ if (newLimits.high < rebalance.limits.low) {
316
+ newLimits.high = rebalance.limits.low
317
+ }
318
+ if (newLimits.high > rebalance.limits.high) {
319
+ newLimits.high = rebalance.limits.high
320
+ }
321
+
322
+ // ================================================================
323
+
324
+ // get new weights, constrained by extremes
325
+
326
+ // {wholeBU/wholeShare} = D18{BU/share} / D18
327
+ const actualLimits = {
328
+ low: new Decimal(newLimits.low.toString()).div(D18d),
329
+ spot: new Decimal(newLimits.spot.toString()).div(D18d),
330
+ high: new Decimal(newLimits.high.toString()).div(D18d),
331
+ }
332
+
333
+ // D27{tok/BU}
334
+ const newWeights = rebalance.weights.map((weightRange, i) => {
335
+ // {wholeTok/wholeBU} = {USD/wholeShare} * {1} / {wholeBU/wholeShare} / {USD/wholeTok}
336
+ const idealWeight = shareValue
337
+ .mul(targetBasket[i])
338
+ .div(actualLimits.spot)
339
+ .div(prices[i])
340
+
341
+ // D27{tok/BU} = {wholeTok/wholeBU} * D27 * {tok/wholeTok} / {BU/wholeBU}
342
+ const newWeightsD27 = {
343
+ low: bn(
344
+ idealWeight
345
+ .mul(rebalanceTarget.div(actualLimits.low.div(actualLimits.spot))) // add remaining delta into weight
346
+ .mul(D27d)
347
+ .mul(decimalScale[i])
348
+ .div(D18d)
349
+ ),
350
+ spot: bn(idealWeight.mul(D27d).mul(decimalScale[i]).div(D18d)),
351
+ high: bn(
352
+ idealWeight
353
+ .mul(ONE.add(delta).div(actualLimits.high.div(actualLimits.spot))) // add remaining delta into weight
354
+ .mul(D27d)
355
+ .mul(decimalScale[i])
356
+ .div(D18d)
357
+ ),
358
+ }
359
+
360
+ if (newWeightsD27.low < weightRange.low) {
361
+ newWeightsD27.low = weightRange.low
362
+ } else if (newWeightsD27.low > weightRange.high) {
363
+ newWeightsD27.low = weightRange.high
364
+ }
365
+
366
+ if (newWeightsD27.spot < weightRange.low) {
367
+ newWeightsD27.spot = weightRange.low
368
+ } else if (newWeightsD27.spot > weightRange.high) {
369
+ newWeightsD27.spot = weightRange.high
370
+ }
371
+
372
+ if (newWeightsD27.high < weightRange.low) {
373
+ newWeightsD27.high = weightRange.low
374
+ } else if (newWeightsD27.high > weightRange.high) {
375
+ newWeightsD27.high = weightRange.high
376
+ }
377
+
378
+ return newWeightsD27
379
+ })
380
+
381
+ // ================================================================
382
+
383
+ // get new prices, constrained by extremes
384
+
385
+
386
+ // D27{USD/tok}
387
+ const newPrices = rebalance.initialPrices.map((initialPrice, i) => {
388
+ // revert if price out of bounds
389
+ const spotPrice = bn(prices[i].mul(D27d).div(decimalScale[i]))
390
+ if (spotPrice < initialPrice.low || spotPrice > initialPrice.high) {
391
+ throw new Error('spot price out of bounds! auction launcher MUST closeRebalance to prevent loss!')
392
+ }
393
+
394
+ if (rebalance.priceControl == PriceControl.NONE) {
395
+ return initialPrice
396
+ }
397
+
398
+ // D27{USD/tok} = {USD/wholeTok} * D27 / {tok/wholeTok}
399
+ const pricesD27 = {
400
+ low: bn(
401
+ prices[i].mul(ONE.sub(priceError[i])).mul(D27d).div(decimalScale[i])
402
+ ),
403
+ high: bn(
404
+ prices[i].div(ONE.sub(priceError[i])).mul(D27d).div(decimalScale[i])
405
+ ),
406
+ }
407
+
408
+
409
+ // low
410
+ if (pricesD27.low < initialPrice.low) {
411
+ pricesD27.low = initialPrice.low
412
+ }
413
+ if (pricesD27.low > initialPrice.high) {
414
+ pricesD27.low = initialPrice.high
415
+ }
416
+
417
+ // high
418
+ if (pricesD27.high < initialPrice.low) {
419
+ pricesD27.high = initialPrice.low
420
+ }
421
+ if (pricesD27.high > initialPrice.high) {
422
+ pricesD27.high = initialPrice.high
423
+ }
424
+
425
+ if (pricesD27.low == pricesD27.high) {
426
+ throw new Error('no price range')
427
+ }
428
+
429
+ return pricesD27
430
+ })
431
+
432
+ // ================================================================
433
+
434
+ // calculate metrics
435
+
436
+ // {USD} = {1} * {USD/wholeShare} * {wholeShare}
437
+ const valueBeingTraded = rebalanceTarget
438
+ .sub(progression)
439
+ .mul(shareValue)
440
+ .mul(supply)
441
+
442
+ const surplusTokens: string[] = []
443
+ const deficitTokens: string[] = []
444
+
445
+ // update Decimal weightRanges
446
+ // {wholeTok/wholeBU} = D27{tok/BU} * {BU/wholeBU} / {tok/wholeTok} / D27
447
+ weightRanges = newWeights.map((range, i) => {
448
+ return {
449
+ low: new Decimal(range.low.toString())
450
+ .mul(D18d)
451
+ .div(decimalScale[i])
452
+ .div(D27d),
453
+ spot: new Decimal(range.spot.toString())
454
+ .mul(D18d)
455
+ .div(decimalScale[i])
456
+ .div(D27d),
457
+ high: new Decimal(range.high.toString())
458
+ .mul(D18d)
459
+ .div(decimalScale[i])
460
+ .div(D27d),
461
+ }
462
+ })
463
+
464
+ rebalance.tokens.forEach((token, i) => {
465
+ // {wholeTok/wholeShare} = {wholeTok/wholeBU} * {wholeBU/wholeShare}
466
+ const buyUpTo = weightRanges[i].low.mul(actualLimits.low)
467
+ const sellDownTo = weightRanges[i].high.mul(actualLimits.high)
468
+
469
+ if (folio[i].lt(buyUpTo)) {
470
+ deficitTokens.push(token)
471
+ } else if (folio[i].gt(sellDownTo)) {
472
+ surplusTokens.push(token)
473
+ }
474
+ })
475
+
476
+ return [
477
+ {
478
+ rebalanceNonce: rebalance.nonce,
479
+ tokens: rebalance.tokens, // full set of tokens, not pruned to the active buy/sells
480
+ newWeights: newWeights,
481
+ newPrices: newPrices,
482
+ newLimits: newLimits,
483
+ },
484
+ {
485
+ round: round,
486
+ initialProgression: initialProgression.toNumber(),
487
+ absoluteProgression: progression.toNumber(),
488
+ relativeProgression: relativeProgression.toNumber(),
489
+ target: rebalanceTarget.toNumber(),
490
+ auctionSize: valueBeingTraded.toNumber(),
491
+ surplusTokens: surplusTokens,
492
+ deficitTokens: deficitTokens,
493
+ },
494
+ ]
495
+ }