-
Notifications
You must be signed in to change notification settings - Fork 2
/
CurvePoolUtil.sol
240 lines (224 loc) · 9.1 KB
/
CurvePoolUtil.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
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;
import "../interfaces/ICurvePool.sol";
import "../interfaces/IPrincipalToken.sol";
import "openzeppelin-math/Math.sol";
/**
* @title CurvePoolUtil library
* @author Spectra Finance
* @notice Provides miscellaneous utils for computations related to Curve protocol.
*/
library CurvePoolUtil {
using Math for uint256;
error SolutionNotFound();
error FailedToFetchExpectedLPTokenAmount();
error FailedToFetchExpectedCoinAmount();
/// @notice Decimal precision used internally in the Curve AMM
uint256 public constant CURVE_DECIMALS = 18;
/// @notice Base unit for Curve AMM calculations
uint256 public constant CURVE_UNIT = 1e18;
/// @notice Make rounding errors favoring other LPs a tiny bit
uint256 private constant APPROXIMATION_DECREMENT = 1;
/// @notice Maximal number of iterations in the binary search algorithm
uint256 private constant MAX_ITERATIONS_BINSEARCH = 255;
/**
* @notice Returns the expected LP token amount received for depositing given amounts of IBT and PT
* @param _curvePool The address of the Curve Pool in which liquidity will be deposited
* @param _amounts Array containing the amounts of IBT and PT to deposit in the Curve Pool
* @return minMintAmount The amount of expected LP tokens received for depositing the liquidity in the pool
*/
function previewAddLiquidity(
address _curvePool,
uint256[2] memory _amounts
) external view returns (uint256 minMintAmount) {
(bool success, bytes memory responseData) = _curvePool.staticcall(
abi.encodeCall(ICurvePool(address(0)).calc_token_amount, (_amounts))
);
if (!success) {
revert FailedToFetchExpectedLPTokenAmount();
}
minMintAmount = abi.decode(responseData, (uint256));
}
/**
* @notice Returns the IBT and PT amounts received for burning a given amount of LP tokens
* @param _curvePool The address of the curve pool
* @param _lpTokenAmount The amount of the lp token to burn
* @return minAmounts The expected respective amounts of IBT and PT withdrawn from the curve pool
*/
function previewRemoveLiquidity(
address _curvePool,
uint256 _lpTokenAmount
) external view returns (uint256[2] memory minAmounts) {
address lpToken = ICurvePool(_curvePool).token();
uint256 totalSupply = IERC20(lpToken).totalSupply();
(uint256 ibtBalance, uint256 ptBalance) = _getCurvePoolBalances(_curvePool);
// decrement following what Curve is doing
if (_lpTokenAmount > APPROXIMATION_DECREMENT && totalSupply != 0) {
_lpTokenAmount -= APPROXIMATION_DECREMENT;
minAmounts = [
(ibtBalance * _lpTokenAmount) / totalSupply,
(ptBalance * _lpTokenAmount) / totalSupply
];
} else {
minAmounts = [uint256(0), uint256(0)];
}
}
/**
* @notice Returns the amount of coin i received for burning a given amount of LP tokens
* @param _curvePool The address of the curve pool
* @param _lpTokenAmount The amount of the LP tokens to burn
* @param _i The index of the unique coin to withdraw
* @return minAmount The expected amount of coin i withdrawn from the curve pool
*/
function previewRemoveLiquidityOneCoin(
address _curvePool,
uint256 _lpTokenAmount,
uint256 _i
) external view returns (uint256 minAmount) {
(bool success, bytes memory responseData) = _curvePool.staticcall(
abi.encodeCall(ICurvePool(address(0)).calc_withdraw_one_coin, (_lpTokenAmount, _i))
);
if (!success) {
revert FailedToFetchExpectedCoinAmount();
}
minAmount = abi.decode(responseData, (uint256));
}
/**
* @notice Return the amount of IBT to deposit in the curve pool, given the total amount of IBT available for deposit
* @param _amount The total amount of IBT available for deposit
* @param _curvePool The address of the pool to deposit the amounts
* @param _pt The address of the PT
* @return ibts The amount of IBT which will be deposited in the curve pool
*/
function calcIBTsToTokenizeForCurvePool(
uint256 _amount,
address _curvePool,
address _pt
) external view returns (uint256 ibts) {
(uint256 ibtBalance, uint256 ptBalance) = _getCurvePoolBalances(_curvePool);
uint256 ibtBalanceInPT = IPrincipalToken(_pt).previewDepositIBT(ibtBalance);
// Liquidity added in a ratio that (closely) matches the existing pool's ratio
ibts = _amount.mulDiv(ptBalance, ibtBalanceInPT + ptBalance);
}
/**
* @param _curvePool : PT/IBT curve pool
* @param _i token index
* @param _j token index
* @param _targetDy amount out desired
* @return dx The amount of token to provide in order to obtain _targetDy after swap
*/
function getDx(
address _curvePool,
uint256 _i,
uint256 _j,
uint256 _targetDy
) external view returns (uint256 dx) {
// Initial guesses
uint256 _minGuess = type(uint256).max;
uint256 _maxGuess = type(uint256).max;
uint256 _factor100;
uint256 _guess = ICurvePool(_curvePool).get_dy(_i, _j, _targetDy);
if (_guess > _targetDy) {
_maxGuess = _targetDy;
_factor100 = 10;
} else {
_minGuess = _targetDy;
_factor100 = 1000;
}
uint256 loops;
_guess = _targetDy;
while (!_dxSolved(_curvePool, _i, _j, _guess, _targetDy, _minGuess, _maxGuess)) {
loops++;
(_minGuess, _maxGuess, _guess) = _runLoop(
_minGuess,
_maxGuess,
_factor100,
_guess,
_targetDy,
_curvePool,
_i,
_j
);
if (loops >= MAX_ITERATIONS_BINSEARCH) {
revert SolutionNotFound();
}
}
dx = _guess;
}
/**
* @dev Runs bisection search
* @param _minGuess lower bound on searched value
* @param _maxGuess upper bound on searched value
* @param _factor100 search interval scaling factor
* @param _guess The previous guess for the `dx` value that is being refined through the search process
* @param _targetDy The target output of the `get_dy` function, which the search aims to achieve by adjusting `dx`.
* @param _curvePool PT/IBT curve pool
* @param _i token index, either 0 or 1
* @param _j token index, either 0 or 1, must be different than _i
* @return The lower bound on _guess, upper bound on _guess and next _guess
*/
function _runLoop(
uint256 _minGuess,
uint256 _maxGuess,
uint256 _factor100,
uint256 _guess,
uint256 _targetDy,
address _curvePool,
uint256 _i,
uint256 _j
) internal view returns (uint256, uint256, uint256) {
if (_minGuess == type(uint256).max || _maxGuess == type(uint256).max) {
_guess = (_guess * _factor100) / 100;
} else {
_guess = (_maxGuess + _minGuess) >> 1;
}
uint256 dy = ICurvePool(_curvePool).get_dy(_i, _j, _guess);
if (dy < _targetDy) {
_minGuess = _guess;
} else if (dy > _targetDy) {
_maxGuess = _guess;
}
return (_minGuess, _maxGuess, _guess);
}
/**
* @dev Returns true if algorithm converged
* @param _curvePool PT/IBT curve pool
* @param _i token index, either 0 or 1
* @param _j token index, either 0 or 1, must be different than _i
* @param _dx The current guess for the `dx` value that is being refined through the search process.
* @param _targetDy The target output of the `get_dy` function, which the search aims to achieve by adjusting `dx`.
* @param _minGuess lower bound on searched value
* @param _maxGuess upper bound on searched value
* @return true if the solution to the search problem was found, false otherwise
*/
function _dxSolved(
address _curvePool,
uint256 _i,
uint256 _j,
uint256 _dx,
uint256 _targetDy,
uint256 _minGuess,
uint256 _maxGuess
) internal view returns (bool) {
if (_minGuess == type(uint256).max || _maxGuess == type(uint256).max) {
return false;
}
uint256 dy = ICurvePool(_curvePool).get_dy(_i, _j, _dx);
if (dy == _targetDy) {
return true;
}
uint256 dy1 = ICurvePool(_curvePool).get_dy(_i, _j, _dx + 1);
if (dy < _targetDy && _targetDy < dy1) {
return true;
}
return false;
}
/**
* @notice Returns the balances of the two tokens in provided curve pool
* @param _curvePool address of the curve pool
* @return The IBT and PT balances of the curve pool
*/
function _getCurvePoolBalances(address _curvePool) internal view returns (uint256, uint256) {
return (ICurvePool(_curvePool).balances(0), ICurvePool(_curvePool).balances(1));
}
}