Skip to content

Commit 67a32cb

Browse files
Merge pull request #42 from euler-xyz/limits
Limits
2 parents 61bdd1e + b7e7c60 commit 67a32cb

File tree

4 files changed

+286
-59
lines changed

4 files changed

+286
-59
lines changed

src/EulerSwap.sol

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ contract EulerSwap is IEulerSwap, EVCUtil {
4949
error Locked();
5050
error Overflow();
5151
error BadParam();
52+
error AmountTooBig();
5253
error DifferentEVC();
5354
error AssetsOutOfOrderOrEqual();
5455
error CurveViolation();
@@ -108,6 +109,8 @@ contract EulerSwap is IEulerSwap, EVCUtil {
108109
callThroughEVC
109110
nonReentrant
110111
{
112+
require(amount0Out <= type(uint112).max && amount1Out <= type(uint112).max, AmountTooBig());
113+
111114
// Optimistically send tokens
112115

113116
if (amount0Out > 0) withdrawAssets(vault0, amount0Out, to);

src/EulerSwapPeriphery.sol

Lines changed: 138 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

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)