Skip to content

Commit 2c93e6b

Browse files
committed
intermediate result
1 parent a8d9996 commit 2c93e6b

File tree

80 files changed

+864
-22522
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+864
-22522
lines changed

contracts/AdaptiveWeightedPool.sol

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
3+
pragma solidity >=0.8.24;
4+
5+
import { ISwapFeePercentageBounds } from "@balancer-labs/v3-interfaces/contracts/vault/ISwapFeePercentageBounds.sol";
6+
import {
7+
IUnbalancedLiquidityInvariantRatioBounds
8+
} from "@balancer-labs/v3-interfaces/contracts/vault/IUnbalancedLiquidityInvariantRatioBounds.sol";
9+
import { IVaultErrors } from "@balancer-labs/v3-interfaces/contracts/vault/IVaultErrors.sol";
10+
import { IBasePool } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePool.sol";
11+
import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol";
12+
import {
13+
IWeightedPool,
14+
WeightedPoolDynamicData,
15+
WeightedPoolImmutableData
16+
} from "@balancer-labs/v3-interfaces/contracts/pool-weighted/IWeightedPool.sol";
17+
import "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol";
18+
19+
import { InputHelpers } from "@balancer-labs/v3-solidity-utils/contracts/helpers/InputHelpers.sol";
20+
import { WeightedMath } from "@balancer-labs/v3-solidity-utils/contracts/math/WeightedMath.sol";
21+
import { BalancerPoolToken } from "@balancer-labs/v3-vault/contracts/BalancerPoolToken.sol";
22+
import { FixedPoint } from "@balancer-labs/v3-solidity-utils/contracts/math/FixedPoint.sol";
23+
import { Version } from "@balancer-labs/v3-solidity-utils/contracts/helpers/Version.sol";
24+
import { PoolInfo } from "@balancer-labs/v3-pool-utils/contracts/PoolInfo.sol";
25+
26+
import { IAdaptiveWeightedPool } from "./interfaces/IAdaptiveWeightedPool.sol";
27+
28+
contract AdaptiveWeightedPool is IAdaptiveWeightedPool, BalancerPoolToken, PoolInfo, Version {
29+
/// @dev Struct with data for deploying a new WeightedPool. `normalizedWeights` length must match `numTokens`.
30+
struct NewPoolParams {
31+
string name;
32+
string symbol;
33+
address wrappedBpt;
34+
uint256[] normalizedWeights;
35+
uint256[] initialVirtualBalances;
36+
string version;
37+
}
38+
39+
/**
40+
* @notice `getRate` from `IRateProvider` was called on a Weighted Pool.
41+
* @dev It is not safe to nest Weighted Pools as WITH_RATE tokens in other pools, where they function as their own
42+
* rate provider. The default `getRate` implementation from `BalancerPoolToken` computes the BPT rate using the
43+
* invariant, which has a non-trivial (and non-linear) error. Without the ability to specify a rounding direction,
44+
* the rate could be manipulable.
45+
*
46+
* It is fine to nest Weighted Pools as STANDARD tokens, or to use them with external rate providers that are
47+
* stable and have at most 1 wei of rounding error (e.g., oracle-based).
48+
*/
49+
error WeightedPoolBptRateUnsupported();
50+
51+
// Fees are 18-decimal, floating point values, which will be stored in the Vault using 24 bits.
52+
// This means they have 0.00001% resolution (i.e., any non-zero bits < 1e11 will cause precision loss).
53+
// Minimum values help make the math well-behaved (i.e., the swap fee should overwhelm any rounding error).
54+
// Maximum values protect users by preventing permissioned actors from setting excessively high swap fees.
55+
uint256 private constant _MIN_SWAP_FEE_PERCENTAGE = 0.001e16; // 0.001%
56+
uint256 private constant _MAX_SWAP_FEE_PERCENTAGE = 10e16; // 10%
57+
uint256 private constant _MAX_TOKENS = 8;
58+
59+
// A minimum normalized weight imposes a maximum weight ratio. We need this due to limitations in the
60+
// implementation of the fixed point power function, as these ratios are often exponents.
61+
uint256 internal constant _MIN_WEIGHT = 1e16; // 1%
62+
63+
address internal immutable _wrappedBpt;
64+
65+
uint256[] internal _weights;
66+
uint256[] internal _targetWeights;
67+
uint256 internal _startChangingTime;
68+
uint256 internal _endChangingTime;
69+
uint256[] internal _initialVirtualBalances; // TODO: optimize storage
70+
uint256[] internal _virtualBalances;
71+
72+
constructor(
73+
NewPoolParams memory params,
74+
IVault vault
75+
) BalancerPoolToken(vault, params.name, params.symbol) PoolInfo(vault) Version(params.version) {
76+
InputHelpers.ensureInputLengthMatch(params.normalizedWeights.length, params.initialVirtualBalances.length);
77+
78+
if (params.wrappedBpt == address(0)) {
79+
revert InvalidWrappedBptLink();
80+
} else if (params.normalizedWeights.length > _MAX_TOKENS) {
81+
revert IVaultErrors.MaxTokens();
82+
}
83+
84+
_wrappedBpt = params.wrappedBpt;
85+
86+
// Ensure each normalized weight is above the minimum.
87+
uint256 normalizedSum = 0;
88+
for (uint8 i = 0; i < params.normalizedWeights.length; ++i) {
89+
uint256 normalizedWeight = params.normalizedWeights[i];
90+
if (normalizedWeight < _MIN_WEIGHT) {
91+
revert MinWeight();
92+
}
93+
94+
normalizedSum += normalizedWeight;
95+
_weights.push(normalizedWeight);
96+
97+
_targetWeights.push(0); // Initialize target weights to zero
98+
_virtualBalances.push(params.initialVirtualBalances[i]);
99+
_initialVirtualBalances.push(params.initialVirtualBalances[i]);
100+
}
101+
102+
// Ensure that the normalized weights sum to ONE.
103+
if (normalizedSum != FixedPoint.ONE) {
104+
revert NormalizedWeightInvariant();
105+
}
106+
}
107+
108+
modifier onlyWrappedBpt() {
109+
if (msg.sender != _wrappedBpt) {
110+
revert SenderNotAllowed();
111+
}
112+
_;
113+
}
114+
115+
/// @inheritdoc IBasePool
116+
function computeInvariant(uint256[] memory balancesLiveScaled18, Rounding rounding) public view returns (uint256) {
117+
function(uint256[] memory, uint256[] memory) internal pure returns (uint256) _upOrDown = rounding ==
118+
Rounding.ROUND_UP
119+
? WeightedMath.computeInvariantUp
120+
: WeightedMath.computeInvariantDown;
121+
122+
for (uint256 i = 0; i < balancesLiveScaled18.length; i++) {
123+
balancesLiveScaled18[i] += _virtualBalances[i];
124+
}
125+
126+
return _upOrDown(_getNormalizedWeights(), balancesLiveScaled18);
127+
}
128+
129+
/// @inheritdoc IBasePool
130+
function computeBalance(
131+
uint256[] memory balancesLiveScaled18,
132+
uint256 tokenInIndex,
133+
uint256 invariantRatio
134+
) external view returns (uint256 newBalance) {
135+
return
136+
WeightedMath.computeBalanceOutGivenInvariant(
137+
balancesLiveScaled18[tokenInIndex] + _virtualBalances[tokenInIndex],
138+
_getNormalizedWeight(tokenInIndex),
139+
invariantRatio
140+
);
141+
}
142+
143+
/// @inheritdoc IWeightedPool
144+
function getNormalizedWeights() external view returns (uint256[] memory) {
145+
return _getNormalizedWeights();
146+
}
147+
148+
/// @inheritdoc IBasePool
149+
function onSwap(PoolSwapParams memory request) public virtual returns (uint256) {
150+
uint256 initialVirtualBalanceTokenIn = _initialVirtualBalances[request.indexIn];
151+
152+
uint256 virtualBalanceTokenIn = _virtualBalances[request.indexIn];
153+
uint256 virtualBalanceTokenOut = _virtualBalances[request.indexOut];
154+
155+
uint256 balanceTokenInScaled18 = request.balancesScaled18[request.indexIn] + virtualBalanceTokenIn;
156+
uint256 balanceTokenOutScaled18 = request.balancesScaled18[request.indexOut] + virtualBalanceTokenOut;
157+
158+
uint256 amountInScaled18;
159+
uint256 amountOutScaled18;
160+
if (request.kind == SwapKind.EXACT_IN) {
161+
amountInScaled18 = request.amountGivenScaled18;
162+
amountOutScaled18 = WeightedMath.computeOutGivenExactIn(
163+
balanceTokenInScaled18,
164+
_getNormalizedWeight(request.indexIn),
165+
balanceTokenOutScaled18,
166+
_getNormalizedWeight(request.indexOut),
167+
amountInScaled18
168+
);
169+
} else {
170+
// Fees are added after scaling happens, to reduce the complexity of the rounding direction analysis.
171+
amountOutScaled18 = request.amountGivenScaled18;
172+
amountInScaled18 = WeightedMath.computeInGivenExactOut(
173+
balanceTokenInScaled18,
174+
_getNormalizedWeight(request.indexIn),
175+
balanceTokenOutScaled18,
176+
_getNormalizedWeight(request.indexOut),
177+
amountOutScaled18
178+
);
179+
}
180+
181+
if (initialVirtualBalanceTokenIn > 0 && virtualBalanceTokenIn > 0) {
182+
if (amountInScaled18 <= virtualBalanceTokenIn) {
183+
virtualBalanceTokenIn -= amountInScaled18;
184+
} else {
185+
virtualBalanceTokenIn = 0;
186+
}
187+
188+
_virtualBalances[request.indexIn] = virtualBalanceTokenIn;
189+
}
190+
191+
return request.kind == SwapKind.EXACT_IN ? amountOutScaled18 : amountInScaled18;
192+
}
193+
194+
function updateWeights(
195+
uint256[] memory newWeights,
196+
uint256 startChangingTime,
197+
uint256 endChangingTime
198+
) external onlyWrappedBpt {
199+
InputHelpers.ensureInputLengthMatch(_weights.length, newWeights.length);
200+
201+
uint256 normalizedSum = 0;
202+
for (uint8 i = 0; i < newWeights.length; ++i) {
203+
uint256 normalizedWeight = newWeights[i];
204+
if (normalizedWeight < _MIN_WEIGHT) {
205+
revert MinWeight();
206+
}
207+
208+
_weights[i] = _computeWeight(i);
209+
_targetWeights[i] = normalizedWeight;
210+
211+
normalizedSum += normalizedWeight;
212+
}
213+
214+
// Ensure that the normalized weights sum to ONE.
215+
if (normalizedSum != FixedPoint.ONE) {
216+
revert NormalizedWeightInvariant();
217+
}
218+
219+
if (startChangingTime < block.timestamp || endChangingTime < startChangingTime) {
220+
revert InvalidTimeRange();
221+
}
222+
223+
_startChangingTime = startChangingTime;
224+
_endChangingTime = endChangingTime;
225+
}
226+
227+
function bootstrapToken(uint256 tokenIndex, uint256 amountScaled18) external onlyWrappedBpt {
228+
_virtualBalances[tokenIndex] += amountScaled18;
229+
}
230+
231+
function getVirtualBalances() external view returns (uint256[] memory virtualBalances) {
232+
return _virtualBalances;
233+
}
234+
235+
function getChangingWeightsInfo()
236+
external
237+
view
238+
returns (
239+
uint256 startChangingTime,
240+
uint256 endChangingTime,
241+
uint256[] memory initialWeights,
242+
uint256[] memory targetWeights
243+
)
244+
{
245+
return (_startChangingTime, _endChangingTime, _weights, _targetWeights);
246+
}
247+
248+
function _getNormalizedWeight(uint256 tokenIndex) internal view virtual returns (uint256) {
249+
if (tokenIndex >= _weights.length) {
250+
revert IVaultErrors.InvalidToken();
251+
}
252+
253+
return _computeWeight(tokenIndex);
254+
}
255+
256+
function _getNormalizedWeights() internal view virtual returns (uint256[] memory) {
257+
uint256[] memory computedWeights = new uint256[](_weights.length);
258+
for (uint256 i = 0; i < _weights.length; i++) {
259+
computedWeights[i] = _computeWeight(i);
260+
}
261+
262+
return computedWeights;
263+
}
264+
265+
function _computeWeight(uint256 tokenIndex) private view returns (uint256) {
266+
if (block.timestamp < _startChangingTime) {
267+
return _weights[tokenIndex];
268+
} else if (block.timestamp >= _endChangingTime) {
269+
return _targetWeights[tokenIndex];
270+
}
271+
272+
uint256 targetWeight = _targetWeights[tokenIndex];
273+
uint256 currentWeight = _weights[tokenIndex];
274+
275+
if (currentWeight == targetWeight) {
276+
return currentWeight;
277+
}
278+
279+
uint256 timeElapsed = block.timestamp - _startChangingTime;
280+
uint256 totalDuration = _endChangingTime - _startChangingTime;
281+
uint256 weightDifference = targetWeight - currentWeight;
282+
283+
return currentWeight + (weightDifference * timeElapsed) / totalDuration;
284+
}
285+
286+
/// @inheritdoc ISwapFeePercentageBounds
287+
function getMinimumSwapFeePercentage() external pure returns (uint256) {
288+
return _MIN_SWAP_FEE_PERCENTAGE;
289+
}
290+
291+
/// @inheritdoc ISwapFeePercentageBounds
292+
function getMaximumSwapFeePercentage() external pure returns (uint256) {
293+
return _MAX_SWAP_FEE_PERCENTAGE;
294+
}
295+
296+
/// @inheritdoc IUnbalancedLiquidityInvariantRatioBounds
297+
function getMinimumInvariantRatio() external pure returns (uint256) {
298+
return WeightedMath._MIN_INVARIANT_RATIO;
299+
}
300+
301+
/// @inheritdoc IUnbalancedLiquidityInvariantRatioBounds
302+
function getMaximumInvariantRatio() external pure returns (uint256) {
303+
return WeightedMath._MAX_INVARIANT_RATIO;
304+
}
305+
306+
/// @inheritdoc IWeightedPool
307+
function getWeightedPoolDynamicData() external view virtual returns (WeightedPoolDynamicData memory data) {
308+
data.balancesLiveScaled18 = _vault.getCurrentLiveBalances(address(this));
309+
(, data.tokenRates) = _vault.getPoolTokenRates(address(this));
310+
data.staticSwapFeePercentage = _vault.getStaticSwapFeePercentage((address(this)));
311+
data.totalSupply = totalSupply();
312+
313+
PoolConfig memory poolConfig = _vault.getPoolConfig(address(this));
314+
data.isPoolInitialized = poolConfig.isPoolInitialized;
315+
data.isPoolPaused = poolConfig.isPoolPaused;
316+
data.isPoolInRecoveryMode = poolConfig.isPoolInRecoveryMode;
317+
}
318+
319+
/// @inheritdoc IWeightedPool
320+
function getWeightedPoolImmutableData() external view virtual returns (WeightedPoolImmutableData memory data) {
321+
data.tokens = _vault.getPoolTokens(address(this));
322+
(data.decimalScalingFactors, ) = _vault.getPoolTokenRates(address(this));
323+
data.normalizedWeights = _getNormalizedWeights();
324+
}
325+
326+
/// @inheritdoc IRateProvider
327+
function getRate() public pure override returns (uint256) {
328+
revert WeightedPoolBptRateUnsupported();
329+
}
330+
}

0 commit comments

Comments
 (0)