forked from balancer/balancer-v2-monorepo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
BaseWeightedPool.sol
486 lines (411 loc) · 18.6 KB
/
BaseWeightedPool.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
// 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;
pragma experimental ABIEncoderV2;
import "@balancer-labs/v2-interfaces/contracts/pool-weighted/WeightedPoolUserData.sol";
import "@balancer-labs/v2-solidity-utils/contracts/math/FixedPoint.sol";
import "@balancer-labs/v2-solidity-utils/contracts/helpers/InputHelpers.sol";
import "@balancer-labs/v2-pool-utils/contracts/BaseMinimalSwapInfoPool.sol";
import "@balancer-labs/v2-pool-utils/contracts/lib/BasePoolMath.sol";
import "./WeightedMath.sol";
/**
* @dev Base class for WeightedPools containing swap, join and exit logic, but leaving storage and management of
* the weights to subclasses. Derived contracts can choose to make weights immutable, mutable, or even dynamic
* based on local or external logic.
*/
abstract contract BaseWeightedPool is BaseMinimalSwapInfoPool {
using FixedPoint for uint256;
using BasePoolUserData for bytes;
using WeightedPoolUserData for bytes;
constructor(
IVault vault,
string memory name,
string memory symbol,
IERC20[] memory tokens,
address[] memory assetManagers,
uint256 swapFeePercentage,
uint256 pauseWindowDuration,
uint256 bufferPeriodDuration,
address owner,
bool mutableTokens
)
BasePool(
vault,
// Given BaseMinimalSwapInfoPool supports both of these specializations, and this Pool never registers
// or deregisters any tokens after construction, picking Two Token when the Pool only has two tokens is free
// gas savings.
// If the pool is expected to be able register new tokens in future, we must choose MINIMAL_SWAP_INFO
// as clearly the TWO_TOKEN specification doesn't support adding extra tokens in future.
tokens.length == 2 && !mutableTokens
? IVault.PoolSpecialization.TWO_TOKEN
: IVault.PoolSpecialization.MINIMAL_SWAP_INFO,
name,
symbol,
tokens,
assetManagers,
swapFeePercentage,
pauseWindowDuration,
bufferPeriodDuration,
owner
)
{
// solhint-disable-previous-line no-empty-blocks
}
// Virtual functions
/**
* @dev Returns the normalized weight of `token`. Weights are fixed point numbers that sum to FixedPoint.ONE.
*/
function _getNormalizedWeight(IERC20 token) internal view virtual returns (uint256);
/**
* @dev Returns all normalized weights, in the same order as the Pool's tokens.
*/
function _getNormalizedWeights() internal view virtual returns (uint256[] memory);
/**
* @dev Returns the current value of the invariant.
*/
function getInvariant() public view returns (uint256) {
(, uint256[] memory balances, ) = getVault().getPoolTokens(getPoolId());
// Since the Pool hooks always work with upscaled balances, we manually
// upscale here for consistency
_upscaleArray(balances, _scalingFactors());
uint256[] memory normalizedWeights = _getNormalizedWeights();
return WeightedMath._calculateInvariant(normalizedWeights, balances);
}
function getNormalizedWeights() external view returns (uint256[] memory) {
return _getNormalizedWeights();
}
// Base Pool handlers
// Swap
function _onSwapGivenIn(
SwapRequest memory swapRequest,
uint256 currentBalanceTokenIn,
uint256 currentBalanceTokenOut
) internal virtual override returns (uint256) {
return
WeightedMath._calcOutGivenIn(
currentBalanceTokenIn,
_getNormalizedWeight(swapRequest.tokenIn),
currentBalanceTokenOut,
_getNormalizedWeight(swapRequest.tokenOut),
swapRequest.amount
);
}
function _onSwapGivenOut(
SwapRequest memory swapRequest,
uint256 currentBalanceTokenIn,
uint256 currentBalanceTokenOut
) internal virtual override returns (uint256) {
return
WeightedMath._calcInGivenOut(
currentBalanceTokenIn,
_getNormalizedWeight(swapRequest.tokenIn),
currentBalanceTokenOut,
_getNormalizedWeight(swapRequest.tokenOut),
swapRequest.amount
);
}
/**
* @dev Called before any join or exit operation. Returns the Pool's total supply by default, but derived contracts
* may choose to add custom behavior at these steps. This often has to do with protocol fee processing.
*/
function _beforeJoinExit(uint256[] memory preBalances, uint256[] memory normalizedWeights)
internal
virtual
returns (uint256, uint256)
{
return (totalSupply(), WeightedMath._calculateInvariant(normalizedWeights, preBalances));
}
/**
* @dev Called after any regular join or exit operation. Empty by default, but derived contracts
* may choose to add custom behavior at these steps. This often has to do with protocol fee processing.
*
* If performing a join operation, balanceDeltas are the amounts in: otherwise they are the amounts out.
*
* This function is free to mutate the `preBalances` array.
*/
function _afterJoinExit(
uint256 preJoinExitInvariant,
uint256[] memory preBalances,
uint256[] memory balanceDeltas,
uint256[] memory normalizedWeights,
uint256 preJoinExitSupply,
uint256 postJoinExitSupply
) internal virtual {
// solhint-disable-previous-line no-empty-blocks
}
// Derived contracts may call this to update state after a join or exit.
function _updatePostJoinExit(uint256 postJoinExitInvariant) internal virtual {
// solhint-disable-previous-line no-empty-blocks
}
// Initialize
function _onInitializePool(
bytes32,
address,
address,
uint256[] memory scalingFactors,
bytes memory userData
) internal virtual override returns (uint256, uint256[] memory) {
WeightedPoolUserData.JoinKind kind = userData.joinKind();
_require(kind == WeightedPoolUserData.JoinKind.INIT, Errors.UNINITIALIZED);
uint256[] memory amountsIn = userData.initialAmountsIn();
InputHelpers.ensureInputLengthMatch(amountsIn.length, scalingFactors.length);
_upscaleArray(amountsIn, scalingFactors);
uint256[] memory normalizedWeights = _getNormalizedWeights();
uint256 invariantAfterJoin = WeightedMath._calculateInvariant(normalizedWeights, amountsIn);
// Set the initial BPT to the value of the invariant times the number of tokens. This makes BPT supply more
// consistent in Pools with similar compositions but different number of tokens.
uint256 bptAmountOut = Math.mul(invariantAfterJoin, amountsIn.length);
// Initialization is still a join, so we need to do post-join work. Since we are not paying protocol fees,
// and all we need to do is update the invariant, call `_updatePostJoinExit` here instead of `_afterJoinExit`.
_updatePostJoinExit(invariantAfterJoin);
return (bptAmountOut, amountsIn);
}
// Join
function _onJoinPool(
bytes32,
address sender,
address,
uint256[] memory balances,
uint256,
uint256,
uint256[] memory scalingFactors,
bytes memory userData
) internal virtual override returns (uint256, uint256[] memory) {
uint256[] memory normalizedWeights = _getNormalizedWeights();
(uint256 preJoinExitSupply, uint256 preJoinExitInvariant) = _beforeJoinExit(balances, normalizedWeights);
(uint256 bptAmountOut, uint256[] memory amountsIn) = _doJoin(
sender,
balances,
normalizedWeights,
scalingFactors,
preJoinExitSupply,
userData
);
// _doJoin performs actions specific to type of join
// but it's a view function so can not emit event
WeightedPoolUserData.JoinKind kind = userData.joinKind();
if(kind == WeightedPoolUserData.JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT ||
kind == WeightedPoolUserData.JoinKind.TOKEN_IN_FOR_EXACT_BPT_OUT){
emit SwapFeePercentageChanged(getSwapFeePercentage(userData, OperationType.JOIN));
}
_afterJoinExit(
preJoinExitInvariant,
balances,
amountsIn,
normalizedWeights,
preJoinExitSupply,
preJoinExitSupply.add(bptAmountOut)
);
return (bptAmountOut, amountsIn);
}
/**
* @dev Dispatch code which decodes the provided userdata to perform the specified join type.
* Inheriting contracts may override this function to add additional join types or extra conditions to allow
* or disallow joins under certain circumstances.
*/
function _doJoin(
address,
uint256[] memory balances,
uint256[] memory normalizedWeights,
uint256[] memory scalingFactors,
uint256 totalSupply,
bytes memory userData
) internal view virtual returns (uint256, uint256[] memory) {
WeightedPoolUserData.JoinKind kind = userData.joinKind();
if (kind == WeightedPoolUserData.JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT) {
return _joinExactTokensInForBPTOut(balances, normalizedWeights, scalingFactors, totalSupply, userData);
} else if (kind == WeightedPoolUserData.JoinKind.TOKEN_IN_FOR_EXACT_BPT_OUT) {
return _joinTokenInForExactBPTOut(balances, normalizedWeights, totalSupply, userData);
} else if (kind == WeightedPoolUserData.JoinKind.ALL_TOKENS_IN_FOR_EXACT_BPT_OUT) {
return _joinAllTokensInForExactBPTOut(balances, totalSupply, userData);
} else {
_revert(Errors.UNHANDLED_JOIN_KIND);
}
}
function _joinExactTokensInForBPTOut(
uint256[] memory balances,
uint256[] memory normalizedWeights,
uint256[] memory scalingFactors,
uint256 totalSupply,
bytes memory userData
) private view returns (uint256, uint256[] memory) {
(uint256[] memory amountsIn, uint256 minBPTAmountOut) = userData.exactTokensInForBptOut();
InputHelpers.ensureInputLengthMatch(balances.length, amountsIn.length);
_upscaleArray(amountsIn, scalingFactors);
uint256 bptAmountOut = WeightedMath._calcBptOutGivenExactTokensIn(
balances,
normalizedWeights,
amountsIn,
totalSupply,
getSwapFeePercentage(userData, OperationType.JOIN)
);
_require(bptAmountOut >= minBPTAmountOut, Errors.BPT_OUT_MIN_AMOUNT);
return (bptAmountOut, amountsIn);
}
function _joinTokenInForExactBPTOut(
uint256[] memory balances,
uint256[] memory normalizedWeights,
uint256 totalSupply,
bytes memory userData
) private view returns (uint256, uint256[] memory) {
(uint256 bptAmountOut, uint256 tokenIndex) = userData.tokenInForExactBptOut();
// Note that there is no maximum amountIn parameter: this is handled by `IVault.joinPool`.
_require(tokenIndex < balances.length, Errors.OUT_OF_BOUNDS);
uint256 amountIn = WeightedMath._calcTokenInGivenExactBptOut(
balances[tokenIndex],
normalizedWeights[tokenIndex],
bptAmountOut,
totalSupply,
getSwapFeePercentage(userData, OperationType.JOIN)
);
// We join in a single token, so we initialize amountsIn with zeros
uint256[] memory amountsIn = new uint256[](balances.length);
// And then assign the result to the selected token
amountsIn[tokenIndex] = amountIn;
return (bptAmountOut, amountsIn);
}
function _joinAllTokensInForExactBPTOut(
uint256[] memory balances,
uint256 totalSupply,
bytes memory userData
) private pure returns (uint256, uint256[] memory) {
uint256 bptAmountOut = userData.allTokensInForExactBptOut();
// Note that there is no maximum amountsIn parameter: this is handled by `IVault.joinPool`.
uint256[] memory amountsIn = BasePoolMath.computeProportionalAmountsIn(balances, totalSupply, bptAmountOut);
return (bptAmountOut, amountsIn);
}
// Exit
function _onExitPool(
bytes32,
address sender,
address,
uint256[] memory balances,
uint256,
uint256,
uint256[] memory scalingFactors,
bytes memory userData
) internal virtual override returns (uint256, uint256[] memory) {
uint256[] memory normalizedWeights = _getNormalizedWeights();
(uint256 preJoinExitSupply, uint256 preJoinExitInvariant) = _beforeJoinExit(balances, normalizedWeights);
(uint256 bptAmountIn, uint256[] memory amountsOut) = _doExit(
sender,
balances,
normalizedWeights,
scalingFactors,
preJoinExitSupply,
userData
);
// _doExit performs actions specific to type of exit
// but it's a view function so can not emit event
WeightedPoolUserData.ExitKind kind = userData.exitKind();
if(kind == WeightedPoolUserData.ExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT ||
kind == WeightedPoolUserData.ExitKind.BPT_IN_FOR_EXACT_TOKENS_OUT){
emit SwapFeePercentageChanged(getSwapFeePercentage(userData, OperationType.EXIT));
}
_afterJoinExit(
preJoinExitInvariant,
balances,
amountsOut,
normalizedWeights,
preJoinExitSupply,
preJoinExitSupply.sub(bptAmountIn)
);
return (bptAmountIn, amountsOut);
}
/**
* @dev Dispatch code which decodes the provided userdata to perform the specified exit type.
* Inheriting contracts may override this function to add additional exit types or extra conditions to allow
* or disallow exit under certain circumstances.
*/
function _doExit(
address,
uint256[] memory balances,
uint256[] memory normalizedWeights,
uint256[] memory scalingFactors,
uint256 totalSupply,
bytes memory userData
) internal view virtual returns (uint256, uint256[] memory) {
WeightedPoolUserData.ExitKind kind = userData.exitKind();
if (kind == WeightedPoolUserData.ExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT) {
return _exitExactBPTInForTokenOut(balances, normalizedWeights, totalSupply, userData);
} else if (kind == WeightedPoolUserData.ExitKind.EXACT_BPT_IN_FOR_TOKENS_OUT) {
return _exitExactBPTInForTokensOut(balances, totalSupply, userData);
} else if (kind == WeightedPoolUserData.ExitKind.BPT_IN_FOR_EXACT_TOKENS_OUT) {
return _exitBPTInForExactTokensOut(balances, normalizedWeights, scalingFactors, totalSupply, userData);
} else {
_revert(Errors.UNHANDLED_EXIT_KIND);
}
}
function _exitExactBPTInForTokenOut(
uint256[] memory balances,
uint256[] memory normalizedWeights,
uint256 totalSupply,
bytes memory userData
) private view returns (uint256, uint256[] memory) {
(uint256 bptAmountIn, uint256 tokenIndex) = userData.exactBptInForTokenOut();
// Note that there is no minimum amountOut parameter: this is handled by `IVault.exitPool`.
_require(tokenIndex < balances.length, Errors.OUT_OF_BOUNDS);
uint256 amountOut = WeightedMath._calcTokenOutGivenExactBptIn(
balances[tokenIndex],
normalizedWeights[tokenIndex],
bptAmountIn,
totalSupply,
getSwapFeePercentage(userData, OperationType.EXIT)
);
// This is an exceptional situation in which the fee is charged on a token out instead of a token in.
// We exit in a single token, so we initialize amountsOut with zeros
uint256[] memory amountsOut = new uint256[](balances.length);
// And then assign the result to the selected token
amountsOut[tokenIndex] = amountOut;
return (bptAmountIn, amountsOut);
}
function _exitExactBPTInForTokensOut(
uint256[] memory balances,
uint256 totalSupply,
bytes memory userData
) private pure returns (uint256, uint256[] memory) {
uint256 bptAmountIn = userData.exactBptInForTokensOut();
// Note that there is no minimum amountOut parameter: this is handled by `IVault.exitPool`.
uint256[] memory amountsOut = BasePoolMath.computeProportionalAmountsOut(balances, totalSupply, bptAmountIn);
return (bptAmountIn, amountsOut);
}
function _exitBPTInForExactTokensOut(
uint256[] memory balances,
uint256[] memory normalizedWeights,
uint256[] memory scalingFactors,
uint256 totalSupply,
bytes memory userData
) private view returns (uint256, uint256[] memory) {
(uint256[] memory amountsOut, uint256 maxBPTAmountIn) = userData.bptInForExactTokensOut();
InputHelpers.ensureInputLengthMatch(amountsOut.length, balances.length);
_upscaleArray(amountsOut, scalingFactors);
// This is an exceptional situation in which the fee is charged on a token out instead of a token in.
uint256 bptAmountIn = WeightedMath._calcBptInGivenExactTokensOut(
balances,
normalizedWeights,
amountsOut,
totalSupply,
getSwapFeePercentage(userData, OperationType.EXIT)
);
_require(bptAmountIn <= maxBPTAmountIn, Errors.BPT_IN_MAX_AMOUNT);
return (bptAmountIn, amountsOut);
}
// Recovery Mode
function _doRecoveryModeExit(
uint256[] memory balances,
uint256 totalSupply,
bytes memory userData
) internal pure override returns (uint256 bptAmountIn, uint256[] memory amountsOut) {
bptAmountIn = userData.recoveryModeExit();
amountsOut = BasePoolMath.computeProportionalAmountsOut(balances, totalSupply, bptAmountIn);
}
}