@@ -12,8 +12,7 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
12
12
13
13
error UnsupportedPair ();
14
14
error OperatorNotInstalled ();
15
- error InsufficientReserves ();
16
- error InsufficientCash ();
15
+ error SwapLimitExceeded ();
17
16
error AmountOutLessThanMin ();
18
17
error AmountInMoreThanMax ();
19
18
@@ -57,6 +56,52 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
57
56
return computeQuote (IEulerSwap (eulerSwap), tokenIn, tokenOut, amountOut, false );
58
57
}
59
58
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
+
60
105
/// @dev Internal function to execute a token swap through EulerSwap
61
106
/// @param eulerSwap The EulerSwap contract address to execute the swap through
62
107
/// @param tokenIn The address of the input token being swapped
@@ -93,35 +138,25 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
93
138
IEVC (eulerSwap.EVC ()).isAccountOperatorAuthorized (eulerSwap.eulerAccount (), address (eulerSwap)),
94
139
OperatorNotInstalled ()
95
140
);
141
+ require (amount <= type (uint112 ).max, SwapLimitExceeded ());
96
142
97
143
uint256 feeMultiplier = eulerSwap.feeMultiplier ();
98
- address vault0 = eulerSwap.vault0 ();
99
- address vault1 = eulerSwap.vault1 ();
100
144
(uint112 reserve0 , uint112 reserve1 ,) = eulerSwap.getReserves ();
101
145
102
146
// exactIn: decrease received amountIn, rounding down
103
147
if (exactIn) amount = amount * feeMultiplier / 1e18 ;
104
148
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);
114
151
115
152
uint256 quote = binarySearch (eulerSwap, reserve0, reserve1, amount, exactIn, asset0IsInput);
116
153
117
154
if (exactIn) {
118
155
// 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 ());
121
157
} else {
122
158
// 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 ());
125
160
}
126
161
127
162
// exactOut: increase required quote(amountIn), rounding up
@@ -169,8 +204,9 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
169
204
170
205
while (low < high) {
171
206
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)) {
174
210
high = mid;
175
211
} else {
176
212
low = mid + 1 ;
@@ -191,54 +227,97 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
191
227
}
192
228
193
229
/**
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
198
236
*
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
211
241
*/
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
+ }
220
254
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
+ }
223
261
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 );
227
265
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;
232
268
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
+ }
235
276
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
+ }
239
279
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 ;
242
299
}
243
300
}
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
+ }
244
323
}
0 commit comments