-
Notifications
You must be signed in to change notification settings - Fork 383
/
LiquidityBootstrappingPool.sol
340 lines (295 loc) · 13 KB
/
LiquidityBootstrappingPool.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
// 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/Math.sol";
import "@balancer-labs/v2-solidity-utils/contracts/helpers/ScalingHelpers.sol";
import "@balancer-labs/v2-pool-utils/contracts/lib/BasePoolMath.sol";
import "@balancer-labs/v2-pool-utils/contracts/lib/PoolRegistrationLib.sol";
import "../WeightedMath.sol";
import "../lib/WeightedExitsLib.sol";
import "../lib/WeightedJoinsLib.sol";
import "./LiquidityBootstrappingPoolSettings.sol";
import "./LiquidityBootstrappingPoolStorageLib.sol";
/**
* @dev Weighted Pool with mutable weights, designed to support V2 Liquidity Bootstrapping.
*/
contract LiquidityBootstrappingPool is LiquidityBootstrappingPoolSettings {
// LiquidityBootstrappingPool change their weights over time: these periods are expected to be long enough (e.g.
// days) that any timestamp manipulation would achieve very little.
// solhint-disable not-rely-on-time
using FixedPoint for uint256;
using BasePoolUserData for bytes;
using WeightedPoolUserData for bytes;
constructor(
IVault vault,
string memory name,
string memory symbol,
IERC20[] memory tokens,
uint256[] memory normalizedWeights,
uint256 swapFeePercentage,
uint256 pauseWindowDuration,
uint256 bufferPeriodDuration,
address owner,
bool swapEnabledOnStart
)
LiquidityBootstrappingPoolSettings(
vault,
PoolRegistrationLib.registerPool(
vault,
tokens.length == 2 ? IVault.PoolSpecialization.TWO_TOKEN : IVault.PoolSpecialization.MINIMAL_SWAP_INFO,
tokens
),
name,
symbol,
tokens,
normalizedWeights,
swapFeePercentage,
pauseWindowDuration,
bufferPeriodDuration,
owner,
swapEnabledOnStart
)
{
// solhint-disable-previous-line no-empty-blocks
}
// Swap Hooks
function _onSwapGeneral(
SwapRequest memory,
uint256[] memory,
uint256,
uint256
) internal virtual override returns (uint256) {
_revert(Errors.UNIMPLEMENTED);
}
function _onSwapMinimal(
SwapRequest memory request,
uint256 balanceTokenIn,
uint256 balanceTokenOut
) internal virtual override returns (uint256) {
uint256 weightTokenIn;
uint256 weightTokenOut;
{
bytes32 poolState = _getPoolState();
_require(LiquidityBootstrappingPoolStorageLib.getSwapEnabled(poolState), Errors.SWAPS_DISABLED);
uint256 pctProgress = LiquidityBootstrappingPoolStorageLib.getWeightChangeProgress(poolState);
weightTokenIn = LiquidityBootstrappingPoolStorageLib.getNormalizedWeightByIndex(
poolState,
_getTokenIndex(request.tokenIn),
pctProgress
);
weightTokenOut = LiquidityBootstrappingPoolStorageLib.getNormalizedWeightByIndex(
poolState,
_getTokenIndex(request.tokenOut),
pctProgress
);
}
uint256 scalingFactorTokenIn = _scalingFactor(request.tokenIn);
uint256 scalingFactorTokenOut = _scalingFactor(request.tokenOut);
balanceTokenIn = _upscale(balanceTokenIn, scalingFactorTokenIn);
balanceTokenOut = _upscale(balanceTokenOut, scalingFactorTokenOut);
if (request.kind == IVault.SwapKind.GIVEN_IN) {
// Fees are subtracted before scaling, to reduce the complexity of the rounding direction analysis.
// This returns amount - fee amount, so we round down (favoring a higher fee amount).
request.amount = request.amount.mulDown(getSwapFeePercentage().complement());
// All token amounts are upscaled.
request.amount = _upscale(request.amount, scalingFactorTokenIn);
uint256 amountOut = WeightedMath._calcOutGivenIn(
balanceTokenIn,
weightTokenIn,
balanceTokenOut,
weightTokenOut,
request.amount
);
// amountOut tokens are exiting the Pool, so we round down.
return _downscaleDown(amountOut, scalingFactorTokenOut);
} else {
// All token amounts are upscaled.
request.amount = _upscale(request.amount, scalingFactorTokenOut);
uint256 amountIn = WeightedMath._calcInGivenOut(
balanceTokenIn,
weightTokenIn,
balanceTokenOut,
weightTokenOut,
request.amount
);
// amountIn tokens are entering the Pool, so we round up.
amountIn = _downscaleUp(amountIn, scalingFactorTokenIn);
// Fees are added after scaling happens, to reduce the complexity of the rounding direction analysis.
// This returns amount + fee amount, so we round up (favoring a higher fee amount).
return amountIn.divUp(getSwapFeePercentage().complement());
}
}
// Initialize hook
function _onInitializePool(
address sender,
address,
bytes memory userData
) internal view override returns (uint256, uint256[] memory) {
// Only the owner can initialize the pool
_require(sender == getOwner(), Errors.CALLER_IS_NOT_LBP_OWNER);
WeightedPoolUserData.JoinKind kind = userData.joinKind();
_require(kind == WeightedPoolUserData.JoinKind.INIT, Errors.UNINITIALIZED);
uint256[] memory amountsIn = userData.initialAmountsIn();
uint256[] memory scalingFactors = getScalingFactors();
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 the BPT supply
// more consistent in Pools with similar token composition, but a different number of tokens.
uint256 bptAmountOut = Math.mul(invariantAfterJoin, amountsIn.length);
return (bptAmountOut, amountsIn);
}
// Join hook
/**
* @dev Called whenever the Pool is joined after the first initialization join (see `_onInitializePool`).
*
* Returns the amount of BPT to mint, the token amounts that the Pool will receive in return, and the number of
* tokens to pay in protocol swap fees.
*
* Implementations of this function might choose to mutate the `balances` array to save gas (e.g. when
* performing intermediate calculations, such as subtraction of due protocol fees). This can be done safely.
*
* Minted BPT will be sent to `recipient`.
*
* The tokens granted to the Pool will be transferred from `sender`. These amounts are considered upscaled and will
* be downscaled (rounding up) before being returned to the Vault.
*/
function _onJoinPool(
address sender,
uint256[] memory balances,
bytes memory userData
) internal view override returns (uint256, uint256[] memory) {
// Only the owner can add liquidity; block public LPs
_require(sender == getOwner(), Errors.CALLER_IS_NOT_LBP_OWNER);
(uint256 bptAmountOut, uint256[] memory amountsIn) = _doJoin(
sender,
balances,
_getNormalizedWeights(),
getScalingFactors(),
totalSupply(),
userData
);
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 returns (uint256, uint256[] memory) {
WeightedPoolUserData.JoinKind kind = userData.joinKind();
if (kind == WeightedPoolUserData.JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT) {
return
WeightedJoinsLib.joinExactTokensInForBPTOut(
balances,
normalizedWeights,
scalingFactors,
totalSupply,
getSwapFeePercentage(),
userData
);
} else if (kind == WeightedPoolUserData.JoinKind.TOKEN_IN_FOR_EXACT_BPT_OUT) {
return
WeightedJoinsLib.joinTokenInForExactBPTOut(
balances,
normalizedWeights,
totalSupply,
getSwapFeePercentage(),
userData
);
} else if (kind == WeightedPoolUserData.JoinKind.ALL_TOKENS_IN_FOR_EXACT_BPT_OUT) {
return WeightedJoinsLib.joinAllTokensInForExactBPTOut(balances, totalSupply, userData);
} else {
_revert(Errors.UNHANDLED_JOIN_KIND);
}
}
// Exit hook
/**
* @dev Called whenever the Pool is exited.
*
* Returns the amount of BPT to burn, the token amounts for each Pool token that the Pool will grant in return, and
* the number of tokens to pay in protocol swap fees.
*
* Implementations of this function might choose to mutate the `balances` array to save gas (e.g. when
* performing intermediate calculations, such as subtraction of due protocol fees). This can be done safely.
*
* BPT will be burnt from `sender`.
*
* The Pool will grant tokens to `recipient`. These amounts are considered upscaled and will be downscaled
* (rounding down) before being returned to the Vault.
*/
function _onExitPool(
address sender,
uint256[] memory balances,
bytes memory userData
) internal view override returns (uint256, uint256[] memory) {
return _doExit(sender, balances, _getNormalizedWeights(), getScalingFactors(), totalSupply(), userData);
}
/**
* @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 returns (uint256, uint256[] memory) {
WeightedPoolUserData.ExitKind kind = userData.exitKind();
if (kind == WeightedPoolUserData.ExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT) {
return
WeightedExitsLib.exitExactBPTInForTokenOut(
balances,
normalizedWeights,
totalSupply,
getSwapFeePercentage(),
userData
);
} else if (kind == WeightedPoolUserData.ExitKind.EXACT_BPT_IN_FOR_TOKENS_OUT) {
return WeightedExitsLib.exitExactBPTInForTokensOut(balances, totalSupply, userData);
} else if (kind == WeightedPoolUserData.ExitKind.BPT_IN_FOR_EXACT_TOKENS_OUT) {
return
WeightedExitsLib.exitBPTInForExactTokensOut(
balances,
normalizedWeights,
scalingFactors,
totalSupply,
getSwapFeePercentage(),
userData
);
} else {
_revert(Errors.UNHANDLED_EXIT_KIND);
}
}
// 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);
}
}