@@ -114,7 +114,7 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
114
114
bool asset0IsInput = checkTokens (eulerSwap, tokenIn, tokenOut);
115
115
(uint256 inLimit , uint256 outLimit ) = calcLimits (eulerSwap, asset0IsInput);
116
116
117
- uint256 quote = binarySearch (eulerSwap, reserve0, reserve1, amount, exactIn, asset0IsInput);
117
+ uint256 quote = search (eulerSwap, reserve0, reserve1, amount, exactIn, asset0IsInput);
118
118
119
119
if (exactIn) {
120
120
// if `exactIn`, `quote` is the amount of assets to buy from the AMM
@@ -130,70 +130,6 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
130
130
return quote;
131
131
}
132
132
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
-
197
133
/**
198
134
* @notice Calculates the maximum input and output amounts for a swap based on protocol constraints
199
135
* @dev Determines limits by checking multiple factors:
@@ -288,4 +224,166 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
288
224
else if (tokenIn == asset1 && tokenOut == asset0) asset0IsInput = false ;
289
225
else revert UnsupportedPair ();
290
226
}
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
+ }
291
389
}
0 commit comments