@@ -12,8 +12,7 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
1212
1313 error UnsupportedPair ();
1414 error OperatorNotInstalled ();
15- error InsufficientReserves ();
16- error InsufficientCash ();
15+ error SwapLimitExceeded ();
1716 error AmountOutLessThanMin ();
1817 error AmountInMoreThanMax ();
1918
@@ -57,6 +56,52 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
5756 return computeQuote (IEulerSwap (eulerSwap), tokenIn, tokenOut, amountOut, false );
5857 }
5958
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+
60105 /// @dev Internal function to execute a token swap through EulerSwap
61106 /// @param eulerSwap The EulerSwap contract address to execute the swap through
62107 /// @param tokenIn The address of the input token being swapped
@@ -93,35 +138,25 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
93138 IEVC (eulerSwap.EVC ()).isAccountOperatorAuthorized (eulerSwap.eulerAccount (), address (eulerSwap)),
94139 OperatorNotInstalled ()
95140 );
141+ require (amount <= type (uint112 ).max, SwapLimitExceeded ());
96142
97143 uint256 feeMultiplier = eulerSwap.feeMultiplier ();
98- address vault0 = eulerSwap.vault0 ();
99- address vault1 = eulerSwap.vault1 ();
100144 (uint112 reserve0 , uint112 reserve1 ,) = eulerSwap.getReserves ();
101145
102146 // exactIn: decrease received amountIn, rounding down
103147 if (exactIn) amount = amount * feeMultiplier / 1e18 ;
104148
105- bool asset0IsInput;
106- {
107- address asset0 = eulerSwap.asset0 ();
108- address asset1 = eulerSwap.asset1 ();
109-
110- if (tokenIn == asset0 && tokenOut == asset1) asset0IsInput = true ;
111- else if (tokenIn == asset1 && tokenOut == asset0) asset0IsInput = false ;
112- else revert UnsupportedPair ();
113- }
149+ bool asset0IsInput = checkTokens (eulerSwap, tokenIn, tokenOut);
150+ (uint256 inLimit , uint256 outLimit ) = calcLimits (eulerSwap, asset0IsInput);
114151
115152 uint256 quote = binarySearch (eulerSwap, reserve0, reserve1, amount, exactIn, asset0IsInput);
116153
117154 if (exactIn) {
118155 // if `exactIn`, `quote` is the amount of assets to buy from the AMM
119- require (quote <= (asset0IsInput ? reserve1 : reserve0), InsufficientReserves ());
120- require (quote <= IEVault (asset0IsInput ? vault1 : vault0).cash (), InsufficientCash ());
156+ require (amount <= inLimit && quote <= outLimit, SwapLimitExceeded ());
121157 } else {
122158 // if `!exactIn`, `amount` is the amount of assets to buy from the AMM
123- require (amount <= (asset0IsInput ? reserve1 : reserve0), InsufficientReserves ());
124- require (amount <= IEVault (asset0IsInput ? vault1 : vault0).cash (), InsufficientCash ());
159+ require (amount <= outLimit && quote <= inLimit, SwapLimitExceeded ());
125160 }
126161
127162 // exactOut: increase required quote(amountIn), rounding up
@@ -169,8 +204,9 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
169204
170205 while (low < high) {
171206 uint256 mid = (low + high) / 2 ;
172- if (dy == 0 ? eulerSwap.verify (uint256 (reserve0New), mid) : eulerSwap.verify (mid, uint256 (reserve1New)))
173- {
207+ (uint256 a , uint256 b ) = dy == 0 ? (uint256 (reserve0New), mid) : (mid, uint256 (reserve1New));
208+ require (a > 0 && b > 0 , SwapLimitExceeded ());
209+ if (eulerSwap.verify (a, b)) {
174210 high = mid;
175211 } else {
176212 low = mid + 1 ;
@@ -191,54 +227,97 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
191227 }
192228
193229 /**
194- * @notice Computes the inverse of the `f()` function for the EulerSwap liquidity curve.
195- * @dev Solves for `x` given `y` using the quadratic formula derived from the liquidity curve:
196- * x = (-b + sqrt(b^2 + 4ac)) / 2a
197- * Utilises mulDiv to avoid overflow and ensures precision with upward rounding.
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
198236 *
199- * @param y The y-coordinate input value (must be greater than `y0`).
200- * @param px Price factor for the x-axis (scaled by 1e18, between 1e18 and 1e36).
201- * @param py Price factor for the y-axis (scaled by 1e18, between 1e18 and 1e36).
202- * @param x0 Reference x-value on the liquidity curve (≤ 2^112 - 1).
203- * @param y0 Reference y-value on the liquidity curve (≤ 2^112 - 1).
204- * @param c Curve parameter shaping liquidity concentration (scaled by 1e18, between 0 and 1e18).
205- *
206- * @return x The computed x-coordinate on the liquidity curve.
207- *
208- * @custom:precision Uses rounding up to maintain precision in all calculations.
209- * @custom:safety FullMath handles potential overflow in the b^2 computation.
210- * @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
211241 */
212- function fInverse (uint256 y , uint256 px , uint256 py , uint256 x0 , uint256 y0 , uint256 c )
213- external
214- pure
215- returns (uint256 )
216- {
217- unchecked {
218- // A component of the quadratic formula: a = 2 * c
219- uint256 A = 2 * c;
242+ function calcLimits (IEulerSwap es , bool asset0IsInput ) internal view returns (uint256 , uint256 ) {
243+ uint256 inLimit = type (uint112 ).max;
244+ uint256 outLimit = type (uint112 ).max;
245+
246+ address eulerAccount = es.eulerAccount ();
247+ (IEVault vault0 , IEVault vault1 ) = (IEVault (es.vault0 ()), IEVault (es.vault1 ()));
248+ // Supply caps on input
249+ {
250+ IEVault vault = (asset0IsInput ? vault0 : vault1);
251+ uint256 maxDeposit = vault.debtOf (eulerAccount) + vault.maxDeposit (eulerAccount);
252+ if (maxDeposit < inLimit) inLimit = maxDeposit;
253+ }
220254
221- // B component of the quadratic formula
222- int256 B = int256 ((px * (y - y0) + py - 1 ) / py) - int256 ((x0 * (2 * c - 1e18 ) + 1e18 - 1 ) / 1e18 );
255+ // Remaining reserves of output
256+ {
257+ (uint112 reserve0 , uint112 reserve1 ,) = es.getReserves ();
258+ uint112 reserveLimit = asset0IsInput ? reserve1 : reserve0;
259+ if (reserveLimit < outLimit) outLimit = reserveLimit;
260+ }
223261
224- // B^2 component, using FullMath for overflow safety
225- uint256 absB = B < 0 ? uint256 ( - B) : uint256 (B);
226- uint256 squaredB = Math. mulDiv (absB, absB, 1e18 , Math.Rounding.Ceil );
262+ // Remaining cash and borrow caps in output
263+ {
264+ IEVault vault = (asset0IsInput ? vault1 : vault0 );
227265
228- // 4 * A * C component of the quadratic formula
229- uint256 AC4a = Math.mulDiv (4 * c, (1e18 - c), 1e18 , Math.Rounding.Ceil);
230- uint256 AC4b = Math.mulDiv (x0, x0, 1e18 , Math.Rounding.Ceil);
231- uint256 AC4 = Math.mulDiv (AC4a, AC4b, 1e18 , Math.Rounding.Ceil);
266+ uint256 cash = vault.cash ();
267+ if (cash < outLimit) outLimit = cash;
232268
233- // Discriminant: b^2 + 4ac, scaled up to maintain precision
234- uint256 discriminant = (squaredB + AC4) * 1e18 ;
269+ (, uint16 borrowCap ) = vault.caps ();
270+ uint256 maxWithdraw = decodeCap (uint256 (borrowCap));
271+ maxWithdraw = vault.totalBorrows () > maxWithdraw ? 0 : maxWithdraw - vault.totalBorrows ();
272+ if (maxWithdraw > cash) maxWithdraw = cash;
273+ maxWithdraw += vault.convertToAssets (vault.balanceOf (eulerAccount));
274+ if (maxWithdraw < outLimit) outLimit = maxWithdraw;
275+ }
235276
236- // Square root of the discriminant (rounded up)
237- uint256 sqrt = Math.sqrt (discriminant);
238- sqrt = (sqrt * sqrt < discriminant) ? sqrt + 1 : sqrt;
277+ return (inLimit, outLimit);
278+ }
239279
240- // Compute and return x = fInverse(y) using the quadratic formula
241- return Math.mulDiv (uint256 (int256 (sqrt) - B), 1e18 , A, Math.Rounding.Ceil);
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+ */
292+ function decodeCap (uint256 amountCap ) internal pure returns (uint256 ) {
293+ if (amountCap == 0 ) return type (uint256 ).max;
294+
295+ unchecked {
296+ // Cannot overflow because this is less than 2**256:
297+ // 10**(2**6 - 1) * (2**10 - 1) = 1.023e+66
298+ return 10 ** (amountCap & 63 ) * (amountCap >> 6 ) / 100 ;
242299 }
243300 }
301+
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+ */
311+ function checkTokens (IEulerSwap eulerSwap , address tokenIn , address tokenOut )
312+ internal
313+ view
314+ returns (bool asset0IsInput )
315+ {
316+ address asset0 = eulerSwap.asset0 ();
317+ address asset1 = eulerSwap.asset1 ();
318+
319+ if (tokenIn == asset0 && tokenOut == asset1) asset0IsInput = true ;
320+ else if (tokenIn == asset1 && tokenOut == asset0) asset0IsInput = false ;
321+ else revert UnsupportedPair ();
322+ }
244323}
0 commit comments