-
Notifications
You must be signed in to change notification settings - Fork 386
/
Copy pathWeightedMath.sol
491 lines (407 loc) · 24 KB
/
WeightedMath.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
pragma solidity ^0.7.0;
import "@balancer-labs/v2-solidity-utils/contracts/helpers/InputHelpers.sol";
import "@balancer-labs/v2-solidity-utils/contracts/math/FixedPoint.sol";
import "@balancer-labs/v2-solidity-utils/contracts/math/Math.sol";
// These functions start with an underscore, as if they were part of a contract and not a library. At some point this
// should be fixed.
// solhint-disable private-vars-leading-underscore
library WeightedMath {
using FixedPoint for uint256;
// A minimum normalized weight imposes a maximum weight ratio. We need this due to limitations in the
// implementation of the power function, as these ratios are often exponents.
uint256 internal constant _MIN_WEIGHT = 0.01e18;
// Having a minimum normalized weight imposes a limit on the maximum number of tokens;
// i.e., the largest possible pool is one where all tokens have exactly the minimum weight.
uint256 internal constant _MAX_WEIGHTED_TOKENS = 100;
// Pool limits that arise from limitations in the fixed point power function (and the imposed 1:100 maximum weight
// ratio).
// Swap limits: amounts swapped may not be larger than this percentage of total balance.
uint256 internal constant _MAX_IN_RATIO = 0.3e18;
uint256 internal constant _MAX_OUT_RATIO = 0.3e18;
// Invariant growth limit: non-proportional joins cannot cause the invariant to increase by more than this ratio.
uint256 internal constant _MAX_INVARIANT_RATIO = 3e18;
// Invariant shrink limit: non-proportional exits cannot cause the invariant to decrease by less than this ratio.
uint256 internal constant _MIN_INVARIANT_RATIO = 0.7e18;
// About swap fees on joins and exits:
// Any join or exit that is not perfectly balanced (e.g. all single token joins or exits) is mathematically
// equivalent to a perfectly balanced join or exit followed by a series of swaps. Since these swaps would charge
// swap fees, it follows that (some) joins and exits should as well.
// On these operations, we split the token amounts in 'taxable' and 'non-taxable' portions, where the 'taxable' part
// is the one to which swap fees are applied.
// Invariant is used to collect protocol swap fees by comparing its value between two times.
// So we can round always to the same direction. It is also used to initiate the BPT amount
// and, because there is a minimum BPT, we round down the invariant.
function _calculateInvariant(uint256[] memory normalizedWeights, uint256[] memory balances)
internal
pure
returns (uint256 invariant)
{
/**********************************************************************************************
// invariant _____ //
// wi = weight index i | | wi //
// bi = balance index i | | bi ^ = i //
// i = invariant //
**********************************************************************************************/
invariant = FixedPoint.ONE;
for (uint256 i = 0; i < normalizedWeights.length; i++) {
invariant = invariant.mulDown(balances[i].powDown(normalizedWeights[i]));
}
_require(invariant > 0, Errors.ZERO_INVARIANT);
}
// Computes how many tokens can be taken out of a pool if `amountIn` are sent, given the
// current balances and weights.
function _calcOutGivenIn(
uint256 balanceIn,
uint256 weightIn,
uint256 balanceOut,
uint256 weightOut,
uint256 amountIn
) internal pure returns (uint256) {
/**********************************************************************************************
// outGivenIn //
// aO = amountOut //
// bO = balanceOut //
// bI = balanceIn / / bI \ (wI / wO) \ //
// aI = amountIn aO = bO * | 1 - | -------------------------- | ^ | //
// wI = weightIn \ \ ( bI + aI ) / / //
// wO = weightOut //
**********************************************************************************************/
// Amount out, so we round down overall.
// The multiplication rounds down, and the subtrahend (power) rounds up (so the base rounds up too).
// Because bI / (bI + aI) <= 1, the exponent rounds down.
// Cannot exceed maximum in ratio
_require(amountIn <= balanceIn.mulDown(_MAX_IN_RATIO), Errors.MAX_IN_RATIO);
uint256 denominator = balanceIn.add(amountIn);
uint256 base = balanceIn.divUp(denominator);
uint256 exponent = weightIn.divDown(weightOut);
uint256 power = base.powUp(exponent);
return balanceOut.mulDown(power.complement());
}
// Computes how many tokens must be sent to a pool in order to take `amountOut`, given the
// current balances and weights.
function _calcInGivenOut(
uint256 balanceIn,
uint256 weightIn,
uint256 balanceOut,
uint256 weightOut,
uint256 amountOut
) internal pure returns (uint256) {
/**********************************************************************************************
// inGivenOut //
// aO = amountOut //
// bO = balanceOut //
// bI = balanceIn / / bO \ (wO / wI) \ //
// aI = amountIn aI = bI * | | -------------------------- | ^ - 1 | //
// wI = weightIn \ \ ( bO - aO ) / / //
// wO = weightOut //
**********************************************************************************************/
// Amount in, so we round up overall.
// The multiplication rounds up, and the power rounds up (so the base rounds up too).
// Because b0 / (b0 - a0) >= 1, the exponent rounds up.
// Cannot exceed maximum out ratio
_require(amountOut <= balanceOut.mulDown(_MAX_OUT_RATIO), Errors.MAX_OUT_RATIO);
uint256 base = balanceOut.divUp(balanceOut.sub(amountOut));
uint256 exponent = weightOut.divUp(weightIn);
uint256 power = base.powUp(exponent);
// Because the base is larger than one (and the power rounds up), the power should always be larger than one, so
// the following subtraction should never revert.
uint256 ratio = power.sub(FixedPoint.ONE);
return balanceIn.mulUp(ratio);
}
function _calcBptOutGivenExactTokensIn(
uint256[] memory balances,
uint256[] memory normalizedWeights,
uint256[] memory amountsIn,
uint256 bptTotalSupply,
uint256 swapFeePercentage
) internal pure returns (uint256) {
// BPT out, so we round down overall.
uint256[] memory balanceRatiosWithFee = new uint256[](amountsIn.length);
uint256 invariantRatioWithFees = 0;
for (uint256 i = 0; i < balances.length; i++) {
balanceRatiosWithFee[i] = balances[i].add(amountsIn[i]).divDown(balances[i]);
invariantRatioWithFees = invariantRatioWithFees.add(balanceRatiosWithFee[i].mulDown(normalizedWeights[i]));
}
uint256 invariantRatio = _computeJoinExactTokensInInvariantRatio(
balances,
normalizedWeights,
amountsIn,
balanceRatiosWithFee,
invariantRatioWithFees,
swapFeePercentage
);
uint256 bptOut = (invariantRatio > FixedPoint.ONE)
? bptTotalSupply.mulDown(invariantRatio - FixedPoint.ONE)
: 0;
return bptOut;
}
function _calcBptOutGivenExactTokenIn(
uint256 balance,
uint256 normalizedWeight,
uint256 amountIn,
uint256 bptTotalSupply,
uint256 swapFeePercentage
) internal pure returns (uint256) {
// BPT out, so we round down overall.
uint256 amountInWithoutFee;
{
uint256 balanceRatioWithFee = balance.add(amountIn).divDown(balance);
// The use of `normalizedWeight.complement()` assumes that the sum of all weights equals FixedPoint.ONE.
// This may not be the case when weights are stored in a denormalized format or during a gradual weight
// change due rounding errors during normalization or interpolation. This will result in a small difference
// between the output of this function and the equivalent `_calcBptOutGivenExactTokensIn` call.
uint256 invariantRatioWithFees = balanceRatioWithFee.mulDown(normalizedWeight).add(
normalizedWeight.complement()
);
if (balanceRatioWithFee > invariantRatioWithFees) {
uint256 nonTaxableAmount = invariantRatioWithFees > FixedPoint.ONE
? balance.mulDown(invariantRatioWithFees - FixedPoint.ONE)
: 0;
uint256 taxableAmount = amountIn.sub(nonTaxableAmount);
uint256 swapFee = taxableAmount.mulUp(swapFeePercentage);
amountInWithoutFee = nonTaxableAmount.add(taxableAmount.sub(swapFee));
} else {
amountInWithoutFee = amountIn;
// If a token's amount in is not being charged a swap fee then it might be zero.
// In this case, it's clear that the sender should receive no BPT.
if (amountInWithoutFee == 0) {
return 0;
}
}
}
uint256 balanceRatio = balance.add(amountInWithoutFee).divDown(balance);
uint256 invariantRatio = balanceRatio.powDown(normalizedWeight);
uint256 bptOut = (invariantRatio > FixedPoint.ONE)
? bptTotalSupply.mulDown(invariantRatio - FixedPoint.ONE)
: 0;
return bptOut;
}
/**
* @dev Intermediate function to avoid stack-too-deep errors.
*/
function _computeJoinExactTokensInInvariantRatio(
uint256[] memory balances,
uint256[] memory normalizedWeights,
uint256[] memory amountsIn,
uint256[] memory balanceRatiosWithFee,
uint256 invariantRatioWithFees,
uint256 swapFeePercentage
) private pure returns (uint256 invariantRatio) {
// Swap fees are charged on all tokens that are being added in a larger proportion than the overall invariant
// increase.
invariantRatio = FixedPoint.ONE;
for (uint256 i = 0; i < balances.length; i++) {
uint256 amountInWithoutFee;
if (balanceRatiosWithFee[i] > invariantRatioWithFees) {
// invariantRatioWithFees might be less than FixedPoint.ONE in edge scenarios due to rounding error,
// particularly if the weights don't exactly add up to 100%.
uint256 nonTaxableAmount = invariantRatioWithFees > FixedPoint.ONE
? balances[i].mulDown(invariantRatioWithFees - FixedPoint.ONE)
: 0;
uint256 swapFee = amountsIn[i].sub(nonTaxableAmount).mulUp(swapFeePercentage);
amountInWithoutFee = amountsIn[i].sub(swapFee);
} else {
amountInWithoutFee = amountsIn[i];
// If a token's amount in is not being charged a swap fee then it might be zero (e.g. when joining a
// Pool with only a subset of tokens). In this case, `balanceRatio` will equal `FixedPoint.ONE`, and
// the `invariantRatio` will not change at all. We therefore skip to the next iteration, avoiding
// the costly `powDown` call.
if (amountInWithoutFee == 0) {
continue;
}
}
uint256 balanceRatio = balances[i].add(amountInWithoutFee).divDown(balances[i]);
invariantRatio = invariantRatio.mulDown(balanceRatio.powDown(normalizedWeights[i]));
}
}
function _calcTokenInGivenExactBptOut(
uint256 balance,
uint256 normalizedWeight,
uint256 bptAmountOut,
uint256 bptTotalSupply,
uint256 swapFeePercentage
) internal pure returns (uint256) {
/******************************************************************************************
// tokenInForExactBPTOut //
// a = amountIn //
// b = balance / / totalBPT + bptOut \ (1 / w) \ //
// bptOut = bptAmountOut a = b * | | -------------------------- | ^ - 1 | //
// bpt = totalBPT \ \ totalBPT / / //
// w = weight //
******************************************************************************************/
// Token in, so we round up overall.
// Calculate the factor by which the invariant will increase after minting BPTAmountOut
uint256 invariantRatio = bptTotalSupply.add(bptAmountOut).divUp(bptTotalSupply);
_require(invariantRatio <= _MAX_INVARIANT_RATIO, Errors.MAX_OUT_BPT_FOR_TOKEN_IN);
// Calculate by how much the token balance has to increase to match the invariantRatio
uint256 balanceRatio = invariantRatio.powUp(FixedPoint.ONE.divUp(normalizedWeight));
uint256 amountInWithoutFee = balance.mulUp(balanceRatio.sub(FixedPoint.ONE));
// We can now compute how much extra balance is being deposited and used in virtual swaps, and charge swap fees
// accordingly.
uint256 taxableAmount = amountInWithoutFee.mulUp(normalizedWeight.complement());
uint256 nonTaxableAmount = amountInWithoutFee.sub(taxableAmount);
uint256 taxableAmountPlusFees = taxableAmount.divUp(swapFeePercentage.complement());
return nonTaxableAmount.add(taxableAmountPlusFees);
}
function _calcBptInGivenExactTokensOut(
uint256[] memory balances,
uint256[] memory normalizedWeights,
uint256[] memory amountsOut,
uint256 bptTotalSupply,
uint256 swapFeePercentage
) internal pure returns (uint256) {
// BPT in, so we round up overall.
uint256[] memory balanceRatiosWithoutFee = new uint256[](amountsOut.length);
uint256 invariantRatioWithoutFees = 0;
for (uint256 i = 0; i < balances.length; i++) {
balanceRatiosWithoutFee[i] = balances[i].sub(amountsOut[i]).divUp(balances[i]);
invariantRatioWithoutFees = invariantRatioWithoutFees.add(
balanceRatiosWithoutFee[i].mulUp(normalizedWeights[i])
);
}
uint256 invariantRatio = _computeExitExactTokensOutInvariantRatio(
balances,
normalizedWeights,
amountsOut,
balanceRatiosWithoutFee,
invariantRatioWithoutFees,
swapFeePercentage
);
return bptTotalSupply.mulUp(invariantRatio.complement());
}
function _calcBptInGivenExactTokenOut(
uint256 balance,
uint256 normalizedWeight,
uint256 amountOut,
uint256 bptTotalSupply,
uint256 swapFeePercentage
) internal pure returns (uint256) {
// BPT in, so we round up overall.
uint256 balanceRatioWithoutFee = balance.sub(amountOut).divUp(balance);
uint256 invariantRatioWithoutFees = balanceRatioWithoutFee.mulUp(normalizedWeight).add(
normalizedWeight.complement()
);
uint256 amountOutWithFee;
if (invariantRatioWithoutFees > balanceRatioWithoutFee) {
// Swap fees are typically charged on 'token in', but there is no 'token in' here, so we apply it to
// 'token out'. This results in slightly larger price impact.
uint256 nonTaxableAmount = balance.mulDown(invariantRatioWithoutFees.complement());
uint256 taxableAmount = amountOut.sub(nonTaxableAmount);
uint256 taxableAmountPlusFees = taxableAmount.divUp(swapFeePercentage.complement());
amountOutWithFee = nonTaxableAmount.add(taxableAmountPlusFees);
} else {
amountOutWithFee = amountOut;
// If a token's amount out is not being charged a swap fee then it might be zero.
// In this case, it's clear that the sender should not send any BPT.
if (amountOutWithFee == 0) {
return 0;
}
}
uint256 balanceRatio = balance.sub(amountOutWithFee).divDown(balance);
uint256 invariantRatio = balanceRatio.powDown(normalizedWeight);
return bptTotalSupply.mulUp(invariantRatio.complement());
}
/**
* @dev Intermediate function to avoid stack-too-deep errors.
*/
function _computeExitExactTokensOutInvariantRatio(
uint256[] memory balances,
uint256[] memory normalizedWeights,
uint256[] memory amountsOut,
uint256[] memory balanceRatiosWithoutFee,
uint256 invariantRatioWithoutFees,
uint256 swapFeePercentage
) private pure returns (uint256 invariantRatio) {
invariantRatio = FixedPoint.ONE;
for (uint256 i = 0; i < balances.length; i++) {
// Swap fees are typically charged on 'token in', but there is no 'token in' here, so we apply it to
// 'token out'. This results in slightly larger price impact.
uint256 amountOutWithFee;
if (invariantRatioWithoutFees > balanceRatiosWithoutFee[i]) {
uint256 nonTaxableAmount = balances[i].mulDown(invariantRatioWithoutFees.complement());
uint256 taxableAmount = amountsOut[i].sub(nonTaxableAmount);
uint256 taxableAmountPlusFees = taxableAmount.divUp(swapFeePercentage.complement());
amountOutWithFee = nonTaxableAmount.add(taxableAmountPlusFees);
} else {
amountOutWithFee = amountsOut[i];
// If a token's amount out is not being charged a swap fee then it might be zero (e.g. when exiting a
// Pool with only a subset of tokens). In this case, `balanceRatio` will equal `FixedPoint.ONE`, and
// the `invariantRatio` will not change at all. We therefore skip to the next iteration, avoiding
// the costly `powDown` call.
if (amountOutWithFee == 0) {
continue;
}
}
uint256 balanceRatio = balances[i].sub(amountOutWithFee).divDown(balances[i]);
invariantRatio = invariantRatio.mulDown(balanceRatio.powDown(normalizedWeights[i]));
}
}
function _calcTokenOutGivenExactBptIn(
uint256 balance,
uint256 normalizedWeight,
uint256 bptAmountIn,
uint256 bptTotalSupply,
uint256 swapFeePercentage
) internal pure returns (uint256) {
/*****************************************************************************************
// exactBPTInForTokenOut //
// a = amountOut //
// b = balance / / totalBPT - bptIn \ (1 / w) \ //
// bptIn = bptAmountIn a = b * | 1 - | -------------------------- | ^ | //
// bpt = totalBPT \ \ totalBPT / / //
// w = weight //
*****************************************************************************************/
// Token out, so we round down overall. The multiplication rounds down, but the power rounds up (so the base
// rounds up). Because (totalBPT - bptIn) / totalBPT <= 1, the exponent rounds down.
// Calculate the factor by which the invariant will decrease after burning BPTAmountIn
uint256 invariantRatio = bptTotalSupply.sub(bptAmountIn).divUp(bptTotalSupply);
_require(invariantRatio >= _MIN_INVARIANT_RATIO, Errors.MIN_BPT_IN_FOR_TOKEN_OUT);
// Calculate by how much the token balance has to decrease to match invariantRatio
uint256 balanceRatio = invariantRatio.powUp(FixedPoint.ONE.divDown(normalizedWeight));
// Because of rounding up, balanceRatio can be greater than one. Using complement prevents reverts.
uint256 amountOutWithoutFee = balance.mulDown(balanceRatio.complement());
// We can now compute how much excess balance is being withdrawn as a result of the virtual swaps, which result
// in swap fees.
// Swap fees are typically charged on 'token in', but there is no 'token in' here, so we apply it
// to 'token out'. This results in slightly larger price impact. Fees are rounded up.
uint256 taxableAmount = amountOutWithoutFee.mulUp(normalizedWeight.complement());
uint256 nonTaxableAmount = amountOutWithoutFee.sub(taxableAmount);
uint256 taxableAmountMinusFees = taxableAmount.mulUp(swapFeePercentage.complement());
return nonTaxableAmount.add(taxableAmountMinusFees);
}
/**
* @dev Calculate the amount of BPT which should be minted when adding a new token to the Pool.
*
* Note that normalizedWeight is set that it corresponds to the desired weight of this token *after* adding it.
* i.e. For a two token 50:50 pool which we want to turn into a 33:33:33 pool, we use a normalized weight of 33%
* @param totalSupply - the total supply of the Pool's BPT.
* @param normalizedWeight - the normalized weight of the token to be added (normalized relative to final weights)
*/
function _calcBptOutAddToken(uint256 totalSupply, uint256 normalizedWeight) internal pure returns (uint256) {
// The amount of BPT which is equivalent to the token being added may be calculated by the growth in the
// sum of the token weights, i.e. if we add a token which will make up 50% of the pool then we should receive
// 50% of the new supply of BPT.
//
// The growth in the total weight of the pool can be easily calculated by:
//
// weightSumRatio = totalWeight / (totalWeight - newTokenWeight)
//
// As we're working with normalized weights `totalWeight` is equal to 1.
uint256 weightSumRatio = FixedPoint.ONE.divDown(FixedPoint.ONE.sub(normalizedWeight));
// The amount of BPT to mint is then simply:
//
// toMint = totalSupply * (weightSumRatio - 1)
return totalSupply.mulDown(weightSumRatio.sub(FixedPoint.ONE));
}
}