Skip to content

Commit 77d549e

Browse files
committed
large refactor
1 parent a2a36da commit 77d549e

9 files changed

+98
-180
lines changed

README.md

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ Maglev is a contract designed to be used as an [EVC operator](https://evc.wtf/do
4646

4747
The following are the high-level steps required to use Maglev:
4848

49-
* Deposit funds into one or both of the vaults in proportion of the initial price
49+
* Deposit funds into one or both of the vaults in proportion to the initial price
5050
* Deploy the desired Maglev contract, choosing parameters such as the vaults, debt limits, and the desired `fee`
5151
* Note that the Maglev contract must be created after the funds are deposited, because its constructor will read the current debts and balances to setup its reserves cache
5252
* Install the Maglev contract as an operator for your account
@@ -114,11 +114,8 @@ With careful parameter selection, the EulerSwap curve supports optimal tradeoffs
114114

115115

116116

117-
## Todo
117+
## Future directions
118118

119-
* The EulerSwap curve has some numerical instability that we believe is caused by rounding
120-
* We think the biggest effect is that it may cause some swaps to fail even if the exact quoted amount is sent. Also, it may result in users slightly overpaying for swaps.
121-
* The code currently has a quotePadding adjustment that seems to prevent this, but since we don't know a hard upper-bound it's hard to say if this solves it in all cases. In particular, this will only work well for 18-decimal tokens of reasonable prices
122119
* Currently we have only been supporting stable-stable pairs
123120
* What extra considerations would there be for floating pairs?
124121
* Automatically re-invest fees. There are a few options:
@@ -145,6 +142,6 @@ With careful parameter selection, the EulerSwap curve supports optimal tradeoffs
145142

146143
## License
147144

148-
(c) 2024 Euler Labs Ltd.
145+
(c) 2024-2025 Euler Labs Ltd.
149146

150147
All rights reserved.

src/MaglevBase.sol

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ abstract contract MaglevBase is IMaglevBase, EVCUtil {
2727
error InsufficientCash();
2828
error DifferentEVC();
2929
error AssetsOutOfOrderOrEqual();
30+
error CurveViolation();
3031

3132
modifier nonReentrant() {
3233
require(locked == 0, Locked());
@@ -125,7 +126,7 @@ abstract contract MaglevBase is IMaglevBase, EVCUtil {
125126
uint256 newReserve1 = reserve1 + amount1In - amount1Out;
126127

127128
require(newReserve0 <= type(uint112).max && newReserve1 <= type(uint112).max, Overflow());
128-
verify(newReserve0, newReserve1);
129+
require(verify(newReserve0, newReserve1), CurveViolation());
129130

130131
reserve0 = uint112(newReserve0);
131132
reserve1 = uint112(newReserve1);
@@ -235,9 +236,55 @@ abstract contract MaglevBase is IMaglevBase, EVCUtil {
235236
return quote;
236237
}
237238

238-
// To be implemented by sub-class
239+
/// @dev May be overridden by subclass if there is a more efficient method
240+
/// of computing quotes than binary search.
241+
function computeQuote(uint256 amount, bool exactIn, bool asset0IsInput)
242+
internal
243+
view
244+
virtual
245+
returns (uint256 output)
246+
{
247+
int256 dx;
248+
int256 dy;
249+
250+
if (exactIn) {
251+
if (asset0IsInput) dx = int256(amount);
252+
else dy = int256(amount);
253+
} else {
254+
if (asset0IsInput) dy = -int256(amount);
255+
else dx = -int256(amount);
256+
}
257+
258+
unchecked {
259+
int256 reserve0New = int256(uint256(reserve0)) + dx;
260+
int256 reserve1New = int256(uint256(reserve1)) + dy;
261+
262+
uint256 low;
263+
uint256 mid;
264+
uint256 high = type(uint112).max;
239265

240-
function verify(uint256 newReserve0, uint256 newReserve1) internal view virtual;
266+
for (uint256 i; i < 256; ++i) {
267+
mid = (low + high) / 2;
268+
bool valid = dx != 0 ? verify(uint256(reserve0New), mid) : verify(mid, uint256(reserve1New));
269+
if (valid) high = mid;
270+
else low = mid + 1;
271+
272+
if (low >= high) break;
273+
}
274+
275+
if (dx != 0) dy = int256(low) - reserve1New;
276+
else dx = int256(low) - reserve0New;
277+
}
278+
279+
if (exactIn) {
280+
if (asset0IsInput) output = uint256(-dy);
281+
else output = uint256(-dx);
282+
} else {
283+
if (asset0IsInput) output = uint256(dx);
284+
else output = uint256(dy);
285+
}
286+
}
241287

242-
function computeQuote(uint256 amount, bool exactIn, bool asset0IsInput) internal view virtual returns (uint256);
288+
/// @dev Must be implemented by sub-class
289+
function verify(uint256 newReserve0, uint256 newReserve1) internal view virtual returns (bool);
243290
}

src/MaglevConstantProduct.sol

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,16 @@ pragma solidity ^0.8.27;
44
import {MaglevBase} from "./MaglevBase.sol";
55

66
contract MaglevConstantProduct is MaglevBase {
7-
error KNotSatisfied();
8-
97
constructor(BaseParams memory baseParams) MaglevBase(baseParams) {}
108

119
function k(uint256 r0, uint256 r1) public pure returns (uint256) {
1210
return r0 * r1;
1311
}
1412

15-
function verify(uint256 newReserve0, uint256 newReserve1) internal view virtual override {
13+
function verify(uint256 newReserve0, uint256 newReserve1) internal view virtual override returns (bool) {
1614
uint256 kBefore = k(reserve0, reserve1);
1715
uint256 kAfter = k(newReserve0, newReserve1);
18-
require(kAfter >= kBefore, KNotSatisfied());
16+
return kAfter >= kBefore;
1917
}
2018

2119
function computeQuote(uint256 amount, bool exactIn, bool asset0IsInput)

src/MaglevConstantSum.sol

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ contract MaglevConstantSum is MaglevBase {
77
uint256 public immutable priceX;
88
uint256 public immutable priceY;
99

10-
error KNotSatisfied();
11-
1210
struct ConstantSumParams {
1311
uint256 priceX;
1412
uint256 priceY;
@@ -23,10 +21,10 @@ contract MaglevConstantSum is MaglevBase {
2321
return (r0 * priceX) + (r1 * priceY);
2422
}
2523

26-
function verify(uint256 newReserve0, uint256 newReserve1) internal view virtual override {
24+
function verify(uint256 newReserve0, uint256 newReserve1) internal view virtual override returns (bool) {
2725
uint256 kBefore = k(reserve0, reserve1);
2826
uint256 kAfter = k(newReserve0, newReserve1);
29-
require(kAfter >= kBefore, KNotSatisfied());
27+
return kAfter >= kBefore;
3028
}
3129

3230
function computeQuote(uint256 amount, bool, bool) internal view virtual override returns (uint256) {

src/MaglevEulerSwap.sol

Lines changed: 10 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// SPDX-License-Identifier: UNLICENSED
22
pragma solidity ^0.8.27;
33

4+
import {Test, console} from "forge-std/Test.sol";
5+
46
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
57
import {MaglevBase} from "./MaglevBase.sol";
68
import {IMaglevEulerSwap} from "./interfaces/IMaglevEulerSwap.sol";
@@ -13,10 +15,6 @@ contract MaglevEulerSwap is IMaglevEulerSwap, MaglevBase {
1315
uint112 public immutable initialReserve0;
1416
uint112 public immutable initialReserve1;
1517

16-
error KNotSatisfied();
17-
error ReservesZero();
18-
error InvalidInputCoordinate();
19-
2018
struct EulerSwapParams {
2119
uint256 priceX;
2220
uint256 priceY;
@@ -34,143 +32,21 @@ contract MaglevEulerSwap is IMaglevEulerSwap, MaglevBase {
3432
initialReserve1 = reserve1;
3533
}
3634

37-
function verify(uint256 newReserve0, uint256 newReserve1) internal view virtual override {
38-
int256 delta = 0;
39-
40-
if (newReserve0 >= initialReserve0) {
41-
delta = int256(newReserve0)
42-
- int256(fy(newReserve1, priceX, priceY, initialReserve0, initialReserve1, concentrationX, concentrationY));
43-
} else {
44-
delta = int256(newReserve1)
45-
- int256(fx(newReserve0, priceX, priceY, initialReserve0, initialReserve1, concentrationX, concentrationY));
46-
}
47-
48-
// if delta is >= zero, then point is on or above the curve
49-
require(delta >= 0, KNotSatisfied());
50-
}
51-
52-
// Due to rounding, computeQuote() may underestimate the amount required to
53-
// pass the verify() function. In order to prevent swaps from failing, quotes
54-
// are inflated by this padding factor.
55-
uint256 private constant quotePadding = 1.00000000001e18;
56-
57-
function computeQuote(uint256 amount, bool exactIn, bool asset0IsInput)
58-
internal
59-
view
60-
virtual
61-
override
62-
returns (uint256 output)
63-
{
64-
int256 dx;
65-
int256 dy;
66-
67-
if (exactIn) {
68-
if (asset0IsInput) dx = int256(amount);
69-
else dy = int256(amount);
70-
} else {
71-
if (asset0IsInput) dy = -int256(amount);
72-
else dx = -int256(amount);
73-
}
74-
75-
{
76-
int256 reserve0New = int256(uint256(reserve0));
77-
int256 reserve1New = int256(uint256(reserve1));
78-
79-
if (dx != 0) {
80-
reserve0New += dx;
81-
reserve1New = int256(
82-
fx(
83-
uint256(reserve0New),
84-
priceX,
85-
priceY,
86-
initialReserve0,
87-
initialReserve1,
88-
concentrationX,
89-
concentrationY
90-
)
91-
);
92-
}
93-
if (dy != 0) {
94-
reserve1New += dy;
95-
reserve0New = int256(
96-
fy(
97-
uint256(reserve1New),
98-
priceX,
99-
priceY,
100-
initialReserve0,
101-
initialReserve1,
102-
concentrationX,
103-
concentrationY
104-
)
105-
);
106-
}
107-
108-
dx = reserve0New - int256(uint256(reserve0));
109-
dy = reserve1New - int256(uint256(reserve1));
110-
}
111-
112-
if (exactIn) {
113-
if (asset0IsInput) output = uint256(-dy);
114-
else output = uint256(-dx);
115-
output = output * 1e18 / quotePadding;
116-
} else {
117-
if (asset0IsInput) output = uint256(dx);
118-
else output = uint256(dy);
119-
output = output * quotePadding / 1e18;
120-
}
121-
}
122-
123-
///// Curve math routines
124-
125-
function fx(uint256 xt, uint256 px, uint256 py, uint256 x0, uint256 y0, uint256 cx, uint256 cy)
35+
function fx(uint256 xt, uint256 px, uint256 py, uint256 x0, uint256 y0, uint256 c)
12636
internal
12737
pure
12838
returns (uint256)
12939
{
130-
require(xt > 0, ReservesZero());
131-
if (xt <= x0) {
132-
return fx1(xt, px, py, x0, y0, cx, cy);
133-
} else {
134-
return fx2(xt, px, py, x0, y0, cx, cy);
135-
}
40+
return y0 + px * 1e18 / py * (c * (2 * x0 - xt) / 1e18 + (1e18 - c) * x0 / 1e18 * x0 / xt - x0) / 1e18;
13641
}
13742

138-
function fy(uint256 yt, uint256 px, uint256 py, uint256 x0, uint256 y0, uint256 cx, uint256 cy)
139-
internal
140-
pure
141-
returns (uint256)
142-
{
143-
require(yt > 0, ReservesZero());
144-
if (yt <= y0) {
145-
return fx1(yt, py, px, y0, x0, cy, cx);
43+
function verify(uint256 newReserve0, uint256 newReserve1) internal view virtual override returns (bool) {
44+
if (newReserve0 >= initialReserve0) {
45+
if (newReserve1 >= initialReserve1) return true;
46+
return newReserve0 >= fx(newReserve1, priceY, priceX, initialReserve1, initialReserve0, concentrationY);
14647
} else {
147-
return fx2(yt, py, px, y0, x0, cy, cx);
48+
if (newReserve1 < initialReserve1) return false;
49+
return newReserve1 >= fx(newReserve0, priceX, priceY, initialReserve0, initialReserve1, concentrationX);
14850
}
14951
}
150-
151-
function fx1(uint256 xt, uint256 px, uint256 py, uint256 x0, uint256 y0, uint256 cx, uint256)
152-
internal
153-
pure
154-
returns (uint256)
155-
{
156-
require(xt <= x0, InvalidInputCoordinate());
157-
return y0 + px * 1e18 / py * (cx * (2 * x0 - xt) / 1e18 + (1e18 - cx) * x0 / 1e18 * x0 / xt - x0) / 1e18;
158-
}
159-
160-
function fx2(uint256 xt, uint256 px, uint256 py, uint256 x0, uint256 y0, uint256, uint256 cy)
161-
internal
162-
pure
163-
returns (uint256)
164-
{
165-
require(xt > x0, InvalidInputCoordinate());
166-
// intermediate values for solving quadratic equation
167-
uint256 a = cy;
168-
int256 b = (int256(px) * 1e18 / int256(py)) * (int256(xt) - int256(x0)) / 1e18
169-
+ int256(y0) * (1e18 - 2 * int256(cy)) / 1e18;
170-
int256 c = (int256(cy) - 1e18) * int256(y0) ** 2 / 1e18 / 1e18;
171-
uint256 discriminant = uint256(int256(uint256(b ** 2)) - 4 * int256(a) * int256(c));
172-
uint256 numerator = uint256(-b + int256(uint256(Math.sqrt(discriminant))));
173-
uint256 denominator = 2 * a;
174-
return numerator * 1e18 / denominator;
175-
}
17652
}

test/ConstantProduct.t.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {TestERC20} from "evk-test/unit/evault/EVaultTestBase.t.sol";
88
import {IEVault} from "evk/EVault/IEVault.sol";
99
import {MaglevTestBase} from "./MaglevTestBase.t.sol";
1010

11-
import {MaglevConstantProduct as Maglev} from "../src/MaglevConstantProduct.sol";
11+
import {MaglevConstantProduct as Maglev, MaglevBase} from "../src/MaglevConstantProduct.sol";
1212

1313
contract ConstantProductTest is MaglevTestBase {
1414
Maglev public maglev;
@@ -39,7 +39,7 @@ contract ConstantProductTest is MaglevTestBase {
3939

4040
assetTST.transfer(address(maglev), amount);
4141

42-
vm.expectRevert(Maglev.KNotSatisfied.selector);
42+
vm.expectRevert(MaglevBase.CurveViolation.selector);
4343
maglev.swap(0, q + 1, address(this), "");
4444

4545
maglev.swap(0, q, address(this), "");
@@ -66,7 +66,7 @@ contract ConstantProductTest is MaglevTestBase {
6666

6767
t1.transfer(address(maglev), amount);
6868

69-
vm.expectRevert(Maglev.KNotSatisfied.selector);
69+
vm.expectRevert(MaglevBase.CurveViolation.selector);
7070
if (dir) maglev.swap(0, q + 1, address(this), "");
7171
else maglev.swap(q + 1, 0, address(this), "");
7272

test/ConstantSum.t.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {TestERC20} from "evk-test/unit/evault/EVaultTestBase.t.sol";
88
import {IEVault} from "evk/EVault/IEVault.sol";
99
import {MaglevTestBase} from "./MaglevTestBase.t.sol";
1010

11-
import {MaglevConstantSum as Maglev} from "../src/MaglevConstantSum.sol";
11+
import {MaglevConstantSum as Maglev, MaglevBase} from "../src/MaglevConstantSum.sol";
1212

1313
contract ConstantSumTest is MaglevTestBase {
1414
Maglev public maglev;
@@ -199,7 +199,7 @@ contract ConstantSumTest is MaglevTestBase {
199199

200200
tt.transfer(address(maglev), needed - 1);
201201

202-
vm.expectRevert(Maglev.KNotSatisfied.selector);
202+
vm.expectRevert(MaglevBase.CurveViolation.selector);
203203
maglev.swap(a1, a2, recipient, "");
204204

205205
tt.transfer(address(maglev), 1);

0 commit comments

Comments
 (0)