Skip to content

Commit a4ed549

Browse files
Merge pull request #46 from euler-xyz/limits-review
Limits PR review
2 parents 1461604 + 2c9c24d commit a4ed549

File tree

2 files changed

+116
-71
lines changed

2 files changed

+116
-71
lines changed

src/EulerSwapPeriphery.sol

Lines changed: 89 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,52 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
5656
return computeQuote(IEulerSwap(eulerSwap), tokenIn, tokenOut, amountOut, false);
5757
}
5858

59+
/// @inheritdoc IEulerSwapPeriphery
60+
function getLimits(address eulerSwap, address tokenIn, address tokenOut) external view returns (uint256, uint256) {
61+
if (
62+
!IEVC(IEulerSwap(eulerSwap).EVC()).isAccountOperatorAuthorized(
63+
IEulerSwap(eulerSwap).eulerAccount(), eulerSwap
64+
)
65+
) return (0, 0);
66+
67+
return calcLimits(IEulerSwap(eulerSwap), checkTokens(IEulerSwap(eulerSwap), tokenIn, tokenOut));
68+
}
69+
70+
/// @inheritdoc IEulerSwapPeriphery
71+
function fInverse(uint256 y, uint256 px, uint256 py, uint256 x0, uint256 y0, uint256 c)
72+
external
73+
pure
74+
returns (uint256)
75+
{
76+
// A component of the quadratic formula: a = 2 * c
77+
uint256 A = 2 * c;
78+
79+
// B component of the quadratic formula
80+
int256 B = int256((px * (y - y0) + py - 1) / py) - int256((x0 * (2 * c - 1e18) + 1e18 - 1) / 1e18);
81+
82+
// B^2 component, using FullMath for overflow safety
83+
uint256 absB = B < 0 ? uint256(-B) : uint256(B);
84+
uint256 squaredB = Math.mulDiv(absB, absB, 1e18, Math.Rounding.Ceil);
85+
86+
// 4 * A * C component of the quadratic formula
87+
uint256 AC4 = Math.mulDiv(
88+
Math.mulDiv(4 * c, (1e18 - c), 1e18, Math.Rounding.Ceil),
89+
Math.mulDiv(x0, x0, 1e18, Math.Rounding.Ceil),
90+
1e18,
91+
Math.Rounding.Ceil
92+
);
93+
94+
// Discriminant: b^2 + 4ac, scaled up to maintain precision
95+
uint256 discriminant = (squaredB + AC4) * 1e18;
96+
97+
// Square root of the discriminant (rounded up)
98+
uint256 sqrt = Math.sqrt(discriminant);
99+
sqrt = (sqrt * sqrt < discriminant) ? sqrt + 1 : sqrt;
100+
101+
// Compute and return x = fInverse(y) using the quadratic formula
102+
return Math.mulDiv(uint256(int256(sqrt) - B), 1e18, A, Math.Rounding.Ceil);
103+
}
104+
59105
/// @dev Internal function to execute a token swap through EulerSwap
60106
/// @param eulerSwap The EulerSwap contract address to execute the swap through
61107
/// @param tokenIn The address of the input token being swapped
@@ -101,7 +147,7 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
101147
if (exactIn) amount = amount * feeMultiplier / 1e18;
102148

103149
bool asset0IsInput = checkTokens(eulerSwap, tokenIn, tokenOut);
104-
(uint256 inLimit, uint256 outLimit) = _getLimits(eulerSwap, asset0IsInput);
150+
(uint256 inLimit, uint256 outLimit) = calcLimits(eulerSwap, asset0IsInput);
105151

106152
uint256 quote = binarySearch(eulerSwap, reserve0, reserve1, amount, exactIn, asset0IsInput);
107153

@@ -181,92 +227,41 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
181227
}
182228

183229
/**
184-
* @notice Computes the inverse of the `f()` function for the EulerSwap liquidity curve.
185-
* @dev Solves for `x` given `y` using the quadratic formula derived from the liquidity curve:
186-
* x = (-b + sqrt(b^2 + 4ac)) / 2a
187-
* Utilises mulDiv to avoid overflow and ensures precision with upward rounding.
188-
*
189-
* @param y The y-coordinate input value (must be greater than `y0`).
190-
* @param px Price factor for the x-axis (scaled by 1e18, between 1e18 and 1e36).
191-
* @param py Price factor for the y-axis (scaled by 1e18, between 1e18 and 1e36).
192-
* @param x0 Reference x-value on the liquidity curve (≤ 2^112 - 1).
193-
* @param y0 Reference y-value on the liquidity curve (≤ 2^112 - 1).
194-
* @param c Curve parameter shaping liquidity concentration (scaled by 1e18, between 0 and 1e18).
230+
* @notice Calculates the maximum input and output amounts for a swap based on protocol constraints
231+
* @dev Determines limits by checking multiple factors:
232+
* 1. Supply caps and existing debt for the input token
233+
* 2. Available reserves in the EulerSwap for the output token
234+
* 3. Available cash and borrow caps for the output token
235+
* 4. Account balances in the respective vaults
195236
*
196-
* @return x The computed x-coordinate on the liquidity curve.
197-
*
198-
* @custom:precision Uses rounding up to maintain precision in all calculations.
199-
* @custom:safety FullMath handles potential overflow in the b^2 computation.
200-
* @custom:requirement Input `y` must be strictly greater than `y0`; otherwise, the function will revert.
237+
* @param es The EulerSwap contract to calculate limits for
238+
* @param asset0IsInput Boolean indicating whether asset0 (true) or asset1 (false) is the input token
239+
* @return uint256 Maximum amount of input token that can be deposited
240+
* @return uint256 Maximum amount of output token that can be withdrawn
201241
*/
202-
function fInverse(uint256 y, uint256 px, uint256 py, uint256 x0, uint256 y0, uint256 c)
203-
external
204-
pure
205-
returns (uint256)
206-
{
207-
// A component of the quadratic formula: a = 2 * c
208-
uint256 A = 2 * c;
209-
210-
// B component of the quadratic formula
211-
int256 B = int256((px * (y - y0) + py - 1) / py) - int256((x0 * (2 * c - 1e18) + 1e18 - 1) / 1e18);
212-
213-
// B^2 component, using FullMath for overflow safety
214-
uint256 absB = B < 0 ? uint256(-B) : uint256(B);
215-
uint256 squaredB = Math.mulDiv(absB, absB, 1e18, Math.Rounding.Ceil);
216-
217-
// 4 * A * C component of the quadratic formula
218-
uint256 AC4 = Math.mulDiv(
219-
Math.mulDiv(4 * c, (1e18 - c), 1e18, Math.Rounding.Ceil),
220-
Math.mulDiv(x0, x0, 1e18, Math.Rounding.Ceil),
221-
1e18,
222-
Math.Rounding.Ceil
223-
);
224-
225-
// Discriminant: b^2 + 4ac, scaled up to maintain precision
226-
uint256 discriminant = (squaredB + AC4) * 1e18;
227-
228-
// Square root of the discriminant (rounded up)
229-
uint256 sqrt = Math.sqrt(discriminant);
230-
sqrt = (sqrt * sqrt < discriminant) ? sqrt + 1 : sqrt;
231-
232-
// Compute and return x = fInverse(y) using the quadratic formula
233-
return Math.mulDiv(uint256(int256(sqrt) - B), 1e18, A, Math.Rounding.Ceil);
234-
}
235-
236-
/// @notice Max amount the pool can buy of tokenIn and sell of tokenOut
237-
function getLimits(address eulerSwap, address tokenIn, address tokenOut)
238-
external
239-
view
240-
returns (uint256 inLimit, uint256 outLimit)
241-
{
242-
return _getLimits(IEulerSwap(eulerSwap), checkTokens(IEulerSwap(eulerSwap), tokenIn, tokenOut));
243-
}
244-
245-
function _getLimits(IEulerSwap es, bool asset0IsInput) internal view returns (uint256 inLimit, uint256 outLimit) {
246-
if (!IEVC(es.EVC()).isAccountOperatorAuthorized(es.eulerAccount(), address(es))) return (0, 0);
247-
248-
inLimit = outLimit = type(uint112).max;
242+
function calcLimits(IEulerSwap es, bool asset0IsInput) internal view returns (uint256, uint256) {
243+
uint256 inLimit = type(uint112).max;
244+
uint256 outLimit = type(uint112).max;
249245

246+
address eulerAccount = es.eulerAccount();
247+
(IEVault vault0, IEVault vault1) = (IEVault(es.vault0()), IEVault(es.vault1()));
250248
// Supply caps on input
251-
252249
{
253-
IEVault vault = IEVault(asset0IsInput ? es.vault0() : es.vault1());
254-
uint256 maxDeposit = vault.debtOf(es.eulerAccount()) + vault.maxDeposit(es.eulerAccount());
250+
IEVault vault = (asset0IsInput ? vault0 : vault1);
251+
uint256 maxDeposit = vault.debtOf(eulerAccount) + vault.maxDeposit(eulerAccount);
255252
if (maxDeposit < inLimit) inLimit = maxDeposit;
256253
}
257254

258255
// Remaining reserves of output
259-
260256
{
261257
(uint112 reserve0, uint112 reserve1,) = es.getReserves();
262258
uint112 reserveLimit = asset0IsInput ? reserve1 : reserve0;
263259
if (reserveLimit < outLimit) outLimit = reserveLimit;
264260
}
265261

266262
// Remaining cash and borrow caps in output
267-
268263
{
269-
IEVault vault = IEVault(asset0IsInput ? es.vault1() : es.vault0());
264+
IEVault vault = (asset0IsInput ? vault1 : vault0);
270265

271266
uint256 cash = vault.cash();
272267
if (cash < outLimit) outLimit = cash;
@@ -275,11 +270,25 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
275270
uint256 maxWithdraw = decodeCap(uint256(borrowCap));
276271
maxWithdraw = vault.totalBorrows() > maxWithdraw ? 0 : maxWithdraw - vault.totalBorrows();
277272
if (maxWithdraw > cash) maxWithdraw = cash;
278-
maxWithdraw += vault.convertToAssets(vault.balanceOf(es.eulerAccount()));
273+
maxWithdraw += vault.convertToAssets(vault.balanceOf(eulerAccount));
279274
if (maxWithdraw < outLimit) outLimit = maxWithdraw;
280275
}
276+
277+
return (inLimit, outLimit);
281278
}
282279

280+
/**
281+
* @notice Decodes a compact-format cap value to its actual numerical value
282+
* @dev The cap uses a compact-format where:
283+
* - If amountCap == 0, there's no cap (returns max uint256)
284+
* - Otherwise, the lower 6 bits represent the exponent (10^exp)
285+
* - The upper bits (>> 6) represent the mantissa
286+
* - The formula is: (10^exponent * mantissa) / 100
287+
* @param amountCap The compact-format cap value to decode
288+
* @return The actual numerical cap value (type(uint256).max if uncapped)
289+
* @custom:security Uses unchecked math for gas optimization as calculations cannot overflow:
290+
* maximum possible value 10^(2^6-1) * (2^10-1) ≈ 1.023e+66 < 2^256
291+
*/
283292
function decodeCap(uint256 amountCap) internal pure returns (uint256) {
284293
if (amountCap == 0) return type(uint256).max;
285294

@@ -290,6 +299,15 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
290299
}
291300
}
292301

302+
/**
303+
* @notice Verifies that the given tokens are supported by the EulerSwap pool and determines swap direction
304+
* @dev Returns a boolean indicating whether the input token is asset0 (true) or asset1 (false)
305+
* @param eulerSwap The EulerSwap pool contract to check against
306+
* @param tokenIn The input token address for the swap
307+
* @param tokenOut The output token address for the swap
308+
* @return asset0IsInput True if tokenIn is asset0 and tokenOut is asset1, false if reversed
309+
* @custom:error UnsupportedPair Thrown if the token pair is not supported by the EulerSwap pool
310+
*/
293311
function checkTokens(IEulerSwap eulerSwap, address tokenIn, address tokenOut)
294312
internal
295313
view

src/interfaces/IEulerSwapPeriphery.sol

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,31 @@ interface IEulerSwapPeriphery {
2121
external
2222
view
2323
returns (uint256);
24+
25+
/// @notice Max amount the pool can buy of tokenIn and sell of tokenOut
26+
function getLimits(address eulerSwap, address tokenIn, address tokenOut) external view returns (uint256, uint256);
27+
28+
/**
29+
* @notice Computes the inverse of the `f()` function for the EulerSwap liquidity curve.
30+
* @dev Solves for `x` given `y` using the quadratic formula derived from the liquidity curve:
31+
* x = (-b + sqrt(b^2 + 4ac)) / 2a
32+
* Utilises mulDiv to avoid overflow and ensures precision with upward rounding.
33+
*
34+
* @param y The y-coordinate input value (must be greater than `y0`).
35+
* @param px Price factor for the x-axis (scaled by 1e18, between 1e18 and 1e36).
36+
* @param py Price factor for the y-axis (scaled by 1e18, between 1e18 and 1e36).
37+
* @param x0 Reference x-value on the liquidity curve (≤ 2^112 - 1).
38+
* @param y0 Reference y-value on the liquidity curve (≤ 2^112 - 1).
39+
* @param c Curve parameter shaping liquidity concentration (scaled by 1e18, between 0 and 1e18).
40+
*
41+
* @return x The computed x-coordinate on the liquidity curve.
42+
*
43+
* @custom:precision Uses rounding up to maintain precision in all calculations.
44+
* @custom:safety FullMath handles potential overflow in the b^2 computation.
45+
* @custom:requirement Input `y` must be strictly greater than `y0`; otherwise, the function will revert.
46+
*/
47+
function fInverse(uint256 y, uint256 px, uint256 py, uint256 x0, uint256 y0, uint256 c)
48+
external
49+
pure
50+
returns (uint256);
2451
}

0 commit comments

Comments
 (0)