-
Notifications
You must be signed in to change notification settings - Fork 19
/
WeightedPool.sol
256 lines (220 loc) · 11.7 KB
/
WeightedPool.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
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
import {
IWeightedPool,
WeightedPoolDynamicData,
WeightedPoolImmutableData
} from "@balancer-labs/v3-interfaces/contracts/pool-weighted/IWeightedPool.sol";
import { ISwapFeePercentageBounds } from "@balancer-labs/v3-interfaces/contracts/vault/ISwapFeePercentageBounds.sol";
import {
IUnbalancedLiquidityInvariantRatioBounds
} from "@balancer-labs/v3-interfaces/contracts/vault/IUnbalancedLiquidityInvariantRatioBounds.sol";
import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol";
import { IVaultErrors } from "@balancer-labs/v3-interfaces/contracts/vault/IVaultErrors.sol";
import { IBasePool } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePool.sol";
import "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol";
import { BalancerPoolToken } from "@balancer-labs/v3-vault/contracts/BalancerPoolToken.sol";
import { PoolInfo } from "@balancer-labs/v3-pool-utils/contracts/PoolInfo.sol";
import { FixedPoint } from "@balancer-labs/v3-solidity-utils/contracts/math/FixedPoint.sol";
import { WeightedMath } from "@balancer-labs/v3-solidity-utils/contracts/math/WeightedMath.sol";
import { InputHelpers } from "@balancer-labs/v3-solidity-utils/contracts/helpers/InputHelpers.sol";
import { Version } from "@balancer-labs/v3-solidity-utils/contracts/helpers/Version.sol";
/**
* @notice Standard Balancer Weighted Pool, with fixed weights.
* @dev Weighted Pools are designed for uncorrelated assets, and use `WeightedMath` (from Balancer v1 and v2)
* to compute the price curve.
*
* There can be up to 8 tokens in a weighted pool (same as v2), and the normalized weights (expressed as 18-decimal
* floating point numbers), must sum to FixedPoint.ONE. Weights cannot be changed after deployment.
*
* The swap fee percentage is bounded by minimum and maximum values (same as were used in v2).
*/
contract WeightedPool is IWeightedPool, BalancerPoolToken, PoolInfo, Version {
/// @dev Struct with data for deploying a new WeightedPool. `normalizedWeights` length must match `numTokens`.
struct NewPoolParams {
string name;
string symbol;
uint256 numTokens;
uint256[] normalizedWeights;
string version;
}
// Fees are 18-decimal, floating point values, which will be stored in the Vault using 24 bits.
// This means they have 0.00001% resolution (i.e., any non-zero bits < 1e11 will cause precision loss).
// Minimum values help make the math well-behaved (i.e., the swap fee should overwhelm any rounding error).
// Maximum values protect users by preventing permissioned actors from setting excessively high swap fees.
uint256 private constant _MIN_SWAP_FEE_PERCENTAGE = 0.001e16; // 0.001%
uint256 private constant _MAX_SWAP_FEE_PERCENTAGE = 10e16; // 10%
// A minimum normalized weight imposes a maximum weight ratio. We need this due to limitations in the
// implementation of the fixed point power function, as these ratios are often exponents.
uint256 private constant _MIN_WEIGHT = 1e16; // 1%
uint256 private immutable _totalTokens;
uint256 internal immutable _normalizedWeight0;
uint256 internal immutable _normalizedWeight1;
uint256 internal immutable _normalizedWeight2;
uint256 internal immutable _normalizedWeight3;
uint256 internal immutable _normalizedWeight4;
uint256 internal immutable _normalizedWeight5;
uint256 internal immutable _normalizedWeight6;
uint256 internal immutable _normalizedWeight7;
/// @notice Indicates that one of the pool tokens' weight is below the minimum allowed.
error MinWeight();
/// @notice Indicates that the sum of the pool tokens' weights is not FixedPoint.ONE.
error NormalizedWeightInvariant();
/**
* @notice `getRate` from `IRateProvider` was called on a Weighted Pool.
* @dev It is not safe to nest Weighted Pools as WITH_RATE tokens in other pools, where they function as their own
* rate provider. The default `getRate` implementation from `BalancerPoolToken` computes the BPT rate using the
* invariant, which has a non-trivial (and non-linear) error. Without the ability to specify a rounding direction,
* the rate could be manipulable.
*
* It is fine to nest Weighted Pools as STANDARD tokens, or to use them with external rate providers that are
* stable and have at most 1 wei of rounding error (e.g., oracle-based).
*/
error WeightedPoolBptRateUnsupported();
constructor(
NewPoolParams memory params,
IVault vault
) BalancerPoolToken(vault, params.name, params.symbol) PoolInfo(vault) Version(params.version) {
_totalTokens = params.numTokens;
InputHelpers.ensureInputLengthMatch(_totalTokens, params.normalizedWeights.length);
// Ensure each normalized weight is above the minimum
uint256 normalizedSum = 0;
for (uint8 i = 0; i < _totalTokens; ++i) {
uint256 normalizedWeight = params.normalizedWeights[i];
if (normalizedWeight < _MIN_WEIGHT) {
revert MinWeight();
}
normalizedSum = normalizedSum + normalizedWeight;
// prettier-ignore
if (i == 0) { _normalizedWeight0 = normalizedWeight; }
else if (i == 1) { _normalizedWeight1 = normalizedWeight; }
else if (i == 2) { _normalizedWeight2 = normalizedWeight; }
else if (i == 3) { _normalizedWeight3 = normalizedWeight; }
else if (i == 4) { _normalizedWeight4 = normalizedWeight; }
else if (i == 5) { _normalizedWeight5 = normalizedWeight; }
else if (i == 6) { _normalizedWeight6 = normalizedWeight; }
else if (i == 7) { _normalizedWeight7 = normalizedWeight; }
}
// Ensure that the normalized weights sum to ONE
if (normalizedSum != FixedPoint.ONE) {
revert NormalizedWeightInvariant();
}
}
/// @inheritdoc IBasePool
function computeInvariant(uint256[] memory balancesLiveScaled18, Rounding rounding) public view returns (uint256) {
function(uint256[] memory, uint256[] memory) internal pure returns (uint256) _upOrDown = rounding ==
Rounding.ROUND_UP
? WeightedMath.computeInvariantUp
: WeightedMath.computeInvariantDown;
return _upOrDown(_getNormalizedWeights(), balancesLiveScaled18);
}
/// @inheritdoc IBasePool
function computeBalance(
uint256[] memory balancesLiveScaled18,
uint256 tokenInIndex,
uint256 invariantRatio
) external view returns (uint256 newBalance) {
return
WeightedMath.computeBalanceOutGivenInvariant(
balancesLiveScaled18[tokenInIndex],
_getNormalizedWeight(tokenInIndex),
invariantRatio
);
}
/// @inheritdoc IWeightedPool
function getNormalizedWeights() external view returns (uint256[] memory) {
return _getNormalizedWeights();
}
/// @inheritdoc IBasePool
function onSwap(PoolSwapParams memory request) public view onlyVault returns (uint256) {
uint256 balanceTokenInScaled18 = request.balancesScaled18[request.indexIn];
uint256 balanceTokenOutScaled18 = request.balancesScaled18[request.indexOut];
if (request.kind == SwapKind.EXACT_IN) {
uint256 amountOutScaled18 = WeightedMath.computeOutGivenExactIn(
balanceTokenInScaled18,
_getNormalizedWeight(request.indexIn),
balanceTokenOutScaled18,
_getNormalizedWeight(request.indexOut),
request.amountGivenScaled18
);
return amountOutScaled18;
} else {
uint256 amountInScaled18 = WeightedMath.computeInGivenExactOut(
balanceTokenInScaled18,
_getNormalizedWeight(request.indexIn),
balanceTokenOutScaled18,
_getNormalizedWeight(request.indexOut),
request.amountGivenScaled18
);
// Fees are added after scaling happens, to reduce the complexity of the rounding direction analysis.
return amountInScaled18;
}
}
function _getNormalizedWeight(uint256 tokenIndex) internal view virtual returns (uint256) {
// prettier-ignore
if (tokenIndex == 0) { return _normalizedWeight0; }
else if (tokenIndex == 1) { return _normalizedWeight1; }
else if (tokenIndex == 2) { return _normalizedWeight2; }
else if (tokenIndex == 3) { return _normalizedWeight3; }
else if (tokenIndex == 4) { return _normalizedWeight4; }
else if (tokenIndex == 5) { return _normalizedWeight5; }
else if (tokenIndex == 6) { return _normalizedWeight6; }
else if (tokenIndex == 7) { return _normalizedWeight7; }
else {
revert IVaultErrors.InvalidToken();
}
}
function _getNormalizedWeights() internal view virtual returns (uint256[] memory) {
uint256 totalTokens = _totalTokens;
uint256[] memory normalizedWeights = new uint256[](totalTokens);
// prettier-ignore
{
normalizedWeights[0] = _normalizedWeight0;
normalizedWeights[1] = _normalizedWeight1;
if (totalTokens > 2) { normalizedWeights[2] = _normalizedWeight2; } else { return normalizedWeights; }
if (totalTokens > 3) { normalizedWeights[3] = _normalizedWeight3; } else { return normalizedWeights; }
if (totalTokens > 4) { normalizedWeights[4] = _normalizedWeight4; } else { return normalizedWeights; }
if (totalTokens > 5) { normalizedWeights[5] = _normalizedWeight5; } else { return normalizedWeights; }
if (totalTokens > 6) { normalizedWeights[6] = _normalizedWeight6; } else { return normalizedWeights; }
if (totalTokens > 7) { normalizedWeights[7] = _normalizedWeight7; }
}
return normalizedWeights;
}
/// @inheritdoc ISwapFeePercentageBounds
function getMinimumSwapFeePercentage() external pure returns (uint256) {
return _MIN_SWAP_FEE_PERCENTAGE;
}
/// @inheritdoc ISwapFeePercentageBounds
function getMaximumSwapFeePercentage() external pure returns (uint256) {
return _MAX_SWAP_FEE_PERCENTAGE;
}
/// @inheritdoc IUnbalancedLiquidityInvariantRatioBounds
function getMinimumInvariantRatio() external pure returns (uint256) {
return WeightedMath._MIN_INVARIANT_RATIO;
}
/// @inheritdoc IUnbalancedLiquidityInvariantRatioBounds
function getMaximumInvariantRatio() external pure returns (uint256) {
return WeightedMath._MAX_INVARIANT_RATIO;
}
/// @inheritdoc IWeightedPool
function getWeightedPoolDynamicData() external view returns (WeightedPoolDynamicData memory data) {
data.balancesLiveScaled18 = _vault.getCurrentLiveBalances(address(this));
(, data.tokenRates) = _vault.getPoolTokenRates(address(this));
data.staticSwapFeePercentage = _vault.getStaticSwapFeePercentage((address(this)));
data.totalSupply = totalSupply();
PoolConfig memory poolConfig = _vault.getPoolConfig(address(this));
data.isPoolInitialized = poolConfig.isPoolInitialized;
data.isPoolPaused = poolConfig.isPoolPaused;
data.isPoolInRecoveryMode = poolConfig.isPoolInRecoveryMode;
}
/// @inheritdoc IWeightedPool
function getWeightedPoolImmutableData() external view returns (WeightedPoolImmutableData memory data) {
data.tokens = _vault.getPoolTokens(address(this));
(data.decimalScalingFactors, ) = _vault.getPoolTokenRates(address(this));
data.normalizedWeights = _getNormalizedWeights();
}
/// @inheritdoc IRateProvider
function getRate() public pure override returns (uint256) {
revert WeightedPoolBptRateUnsupported();
}
}