Skip to content

Commit d20ca38

Browse files
committed
import Michael's search() routine and dependencies
1 parent fce4e34 commit d20ca38

File tree

1 file changed

+163
-65
lines changed

1 file changed

+163
-65
lines changed

src/EulerSwapPeriphery.sol

Lines changed: 163 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
114114
bool asset0IsInput = checkTokens(eulerSwap, tokenIn, tokenOut);
115115
(uint256 inLimit, uint256 outLimit) = calcLimits(eulerSwap, asset0IsInput);
116116

117-
uint256 quote = binarySearch(eulerSwap, reserve0, reserve1, amount, exactIn, asset0IsInput);
117+
uint256 quote = search(eulerSwap, reserve0, reserve1, amount, exactIn, asset0IsInput);
118118

119119
if (exactIn) {
120120
// if `exactIn`, `quote` is the amount of assets to buy from the AMM
@@ -130,70 +130,6 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
130130
return quote;
131131
}
132132

133-
/// @notice Binary searches for the output amount along a swap curve given input parameters
134-
/// @dev General-purpose routine for binary searching swapping curves.
135-
/// Although some curves may have more efficient closed-form solutions,
136-
/// this works with any monotonic curve.
137-
/// @param eulerSwap The EulerSwap contract to search the curve for
138-
/// @param reserve0 Current reserve of asset0 in the pool
139-
/// @param reserve1 Current reserve of asset1 in the pool
140-
/// @param amount The input or output amount depending on exactIn
141-
/// @param exactIn True if amount is input amount, false if amount is output amount
142-
/// @param asset0IsInput True if asset0 is being input, false if asset1 is being input
143-
/// @return output The calculated output amount from the binary search
144-
function binarySearch(
145-
IEulerSwap eulerSwap,
146-
uint112 reserve0,
147-
uint112 reserve1,
148-
uint256 amount,
149-
bool exactIn,
150-
bool asset0IsInput
151-
) internal view returns (uint256 output) {
152-
int256 dx;
153-
int256 dy;
154-
155-
if (exactIn) {
156-
if (asset0IsInput) dx = int256(amount);
157-
else dy = int256(amount);
158-
} else {
159-
if (asset0IsInput) dy = -int256(amount);
160-
else dx = -int256(amount);
161-
}
162-
163-
unchecked {
164-
int256 reserve0New = int256(uint256(reserve0)) + dx;
165-
int256 reserve1New = int256(uint256(reserve1)) + dy;
166-
require(reserve0New > 0 && reserve1New > 0, SwapLimitExceeded());
167-
168-
uint256 low;
169-
uint256 high = type(uint112).max;
170-
171-
while (low < high) {
172-
uint256 mid = (low + high) / 2;
173-
require(mid > 0, SwapLimitExceeded());
174-
(uint256 a, uint256 b) = dy == 0 ? (uint256(reserve0New), mid) : (mid, uint256(reserve1New));
175-
if (eulerSwap.verify(a, b)) {
176-
high = mid;
177-
} else {
178-
low = mid + 1;
179-
}
180-
}
181-
182-
require(high < type(uint112).max, SwapLimitExceeded()); // at least one point verified
183-
184-
if (dx != 0) dy = int256(low) - reserve1New;
185-
else dx = int256(low) - reserve0New;
186-
}
187-
188-
if (exactIn) {
189-
if (asset0IsInput) output = uint256(-dy);
190-
else output = uint256(-dx);
191-
} else {
192-
if (asset0IsInput) output = dx >= 0 ? uint256(dx) : 0;
193-
else output = dy >= 0 ? uint256(dy) : 0;
194-
}
195-
}
196-
197133
/**
198134
* @notice Calculates the maximum input and output amounts for a swap based on protocol constraints
199135
* @dev Determines limits by checking multiple factors:
@@ -288,4 +224,166 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
288224
else if (tokenIn == asset1 && tokenOut == asset0) asset0IsInput = false;
289225
else revert UnsupportedPair();
290226
}
227+
228+
229+
230+
231+
//////////////////////////////
232+
233+
234+
// Exact version starts here.
235+
// Strategy is to re-calculate everything using f() and fInverse() and see where things break.
236+
function search(
237+
IEulerSwap eulerSwap,
238+
uint112 reserve0,
239+
uint112 reserve1,
240+
uint256 amount,
241+
bool exactIn,
242+
bool asset0IsInput
243+
) internal view returns (uint256 output) {
244+
uint256 px = eulerSwap.priceX();
245+
uint256 py = eulerSwap.priceY();
246+
uint256 x0 = eulerSwap.equilibriumReserve0();
247+
uint256 y0 = eulerSwap.equilibriumReserve1();
248+
uint256 cx = eulerSwap.concentrationX();
249+
uint256 cy = eulerSwap.concentrationY();
250+
251+
uint256 xNew;
252+
uint256 yNew;
253+
254+
if (exactIn) {
255+
// exact in
256+
if (asset0IsInput) {
257+
// swap X in and Y out
258+
xNew = reserve0 + amount;
259+
if (xNew < x0) {
260+
// remain on f()
261+
yNew = f(xNew, px, py, x0, y0, cx);
262+
} else {
263+
// move to g()
264+
yNew = fInverse(xNew, py, px, y0, x0, cy);
265+
}
266+
output = reserve1 > yNew ? reserve1 - yNew : 0;
267+
} else {
268+
// swap Y in and X out
269+
yNew = reserve1 + amount;
270+
if (yNew < y0) {
271+
// remain on g()
272+
xNew = f(yNew, py, px, y0, x0, cy);
273+
} else {
274+
// move to f()
275+
xNew = fInverse(yNew, px, py, x0, y0, cx);
276+
}
277+
output = reserve0 > xNew ? reserve0 - xNew : 0;
278+
}
279+
} else {
280+
// exact out
281+
if (asset0IsInput) {
282+
// swap Y out and X in
283+
yNew = reserve1 - amount;
284+
if (yNew < y0) {
285+
// remain on g()
286+
xNew = f(yNew, py, px, y0, x0, cy);
287+
} else {
288+
// move to f()
289+
xNew = fInverse(yNew, px, py, x0, y0, cx);
290+
}
291+
output = xNew > reserve0 ? xNew - reserve0 : 0;
292+
} else {
293+
// swap X out and Y in
294+
xNew = reserve0 - amount;
295+
if (xNew < x0) {
296+
// remain on f()
297+
yNew = f(xNew, py, px, y0, x0, cx);
298+
} else {
299+
// move to g()
300+
yNew = fInverse(xNew, py, px, y0, x0, cy);
301+
}
302+
output = yNew > reserve1 ? yNew - reserve1 : 0;
303+
}
304+
}
305+
}
306+
307+
/// @dev EulerSwap curve definition
308+
/// Pre-conditions: x <= x0, 1 <= {px,py} <= 1e36, {x0,y0} <= type(uint112).max, c <= 1e18
309+
function f(uint256 x, uint256 px, uint256 py, uint256 x0, uint256 y0, uint256 c) internal pure returns (uint256) {
310+
unchecked {
311+
uint256 v = Math.mulDiv(px * (x0 - x), c * x + (1e18 - c) * x0, x * 1e18, Math.Rounding.Ceil);
312+
require(v <= type(uint248).max, "HELP");
313+
return y0 + (v + (py - 1)) / py;
314+
}
315+
}
316+
317+
function fInverse(uint256 y, uint256 px, uint256 py, uint256 x0, uint256 y0, uint256 c)
318+
internal
319+
pure
320+
returns (uint256)
321+
{
322+
// components of quadratic equation
323+
int256 B = int256((py * (y - y0) + (px - 1)) / px) - (2 * int256(c) - int256(1e18)) * int256(x0) / 1e18;
324+
uint256 C;
325+
uint256 fourAC;
326+
if (x0 < 1e18) {
327+
C = ((1e18 - c) * x0 * x0 + (1e18 - 1)) / 1e18; // upper bound of 1e28 for x0 means this is safe
328+
fourAC = Math.mulDiv(4 * c, C, 1e18, Math.Rounding.Ceil);
329+
} else {
330+
C = Math.mulDiv((1e18 - c), x0 * x0, 1e36, Math.Rounding.Ceil); // upper bound of 1e28 for x0 means this is safe
331+
fourAC = Math.mulDiv(4 * c, C, 1, Math.Rounding.Ceil);
332+
}
333+
334+
// solve for the square root
335+
uint256 absB = abs(B);
336+
uint256 squaredB;
337+
uint256 discriminant;
338+
uint256 sqrt;
339+
if (absB > 1e33) {
340+
uint256 scale = computeScale(absB);
341+
squaredB = Math.mulDiv(absB / scale, absB, scale, Math.Rounding.Ceil);
342+
discriminant = squaredB + fourAC / (scale * scale);
343+
sqrt = Math.sqrt(discriminant);
344+
sqrt = (sqrt * sqrt < discriminant) ? sqrt + 1 : sqrt;
345+
sqrt = sqrt * scale;
346+
} else {
347+
squaredB = Math.mulDiv(absB, absB, 1, Math.Rounding.Ceil);
348+
discriminant = squaredB + fourAC; // keep in 1e36 scale for increased precision ahead of sqrt
349+
sqrt = Math.sqrt(discriminant); // drop back to 1e18 scale
350+
sqrt = (sqrt * sqrt < discriminant) ? sqrt + 1 : sqrt;
351+
}
352+
353+
uint256 x;
354+
if (B <= 0) {
355+
x = Math.mulDiv(absB + sqrt, 1e18, 2 * c, Math.Rounding.Ceil) + 3;
356+
} else {
357+
x = Math.mulDiv(2 * C, 1e18, absB + sqrt, Math.Rounding.Ceil) + 3;
358+
}
359+
360+
if (x >= x0) {
361+
return x0;
362+
} else {
363+
return x;
364+
}
365+
}
366+
367+
function computeScale(uint256 x) internal pure returns (uint256 scale) {
368+
uint256 bits = 0;
369+
uint256 tmp = x;
370+
371+
while (tmp > 0) {
372+
tmp >>= 1;
373+
bits++;
374+
}
375+
376+
// absB * absB must be <= 2^256 ⇒ bits(B) ≤ 128
377+
if (bits > 128) {
378+
uint256 excessBits = bits - 128;
379+
// 2^excessBits is how much we need to scale down to prevent overflow
380+
scale = 1 << excessBits;
381+
} else {
382+
scale = 1;
383+
}
384+
}
385+
386+
function abs(int256 x) internal pure returns (uint256) {
387+
return uint256(x >= 0 ? x : -x);
388+
}
291389
}

0 commit comments

Comments
 (0)