Skip to content

Commit d33b6d8

Browse files
committed
use unchecked math in f() function to improve on-chain quoting efficiency
1 parent 8dad69d commit d33b6d8

File tree

4 files changed

+29
-6
lines changed

4 files changed

+29
-6
lines changed

docs/f.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,15 @@ None of the computations for the arguments to `mulDiv` can overflow:
4646
If amounts/prices are large, and we travel too far down the curve, then `mulDiv` (or the subsequent `y0` addition) could overflow because its output value cannot be represented as a `uint256`. However, these output values would never be valid anyway, because they exceed `type(uint112).max`.
4747

4848
To see this, consider the case where `mulDiv` fails due to overflow. This means that its result would've been greater than `2**256 - 1`. Dividing this value by the largest allowed value for `py` (`1e36`) gives approximately `2**136`, which is greater than the maximum allowed amount value of `2**112 - 1`. Both the rounding up operation and the final addition of `y0` can only further *increase* this value. This means that all cases where `mulDiv` or the subsequent additions overflow would involve `f()` returning values that are impossible for a swapper to satisfy, so they would revert anyways.
49+
50+
### Unchecked Math
51+
52+
As-per the previous section, none of the computations of the arguments to `mulDiv` can overflow. To prevent overflows in the remaining operations, the `mulDiv` output is further restricted to `2**248 - 1`:
53+
54+
unchecked {
55+
uint256 v = Math.mulDiv(px * (x0 - x), c * x + (1e18 - c) * x0, x * 1e18, Math.Rounding.Ceil);
56+
require(v <= type(uint248).max, Overflow());
57+
return y0 + (v + (py - 1)) / py;
58+
}
59+
60+
Note that this does not change the analysis of the previous section: Values between `2**248 - 1` and `2**256 - 1` will also never reduce down to the required `2**112 - 1`, so this does not cause any additional failure cases.

src/EulerSwap.sol

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ contract EulerSwap is IEulerSwap, EVCUtil {
4747

4848
error Locked();
4949
error Overflow();
50-
error BadFee();
50+
error BadParam();
5151
error DifferentEVC();
5252
error AssetsOutOfOrderOrEqual();
5353
error CurveViolation();
@@ -64,7 +64,11 @@ contract EulerSwap is IEulerSwap, EVCUtil {
6464
constructor(Params memory params, CurveParams memory curveParams) EVCUtil(IEVault(params.vault0).EVC()) {
6565
// EulerSwap params
6666

67-
require(params.fee < 1e18, BadFee());
67+
require(params.fee < 1e18, BadParam());
68+
require(params.debtLimit0 <= type(uint112).max && params.debtLimit1 <= type(uint112).max, BadParam());
69+
require(curveParams.priceX > 0 && curveParams.priceY > 0, BadParam());
70+
require(curveParams.priceX <= 1e36 && curveParams.priceY <= 1e36, BadParam());
71+
require(curveParams.concentrationX <= 1e18 && curveParams.concentrationY <= 1e18, BadParam());
6872
require(IEVault(params.vault0).EVC() == IEVault(params.vault1).EVC(), DifferentEVC());
6973

7074
address asset0Addr = IEVault(params.vault0).asset();
@@ -240,7 +244,11 @@ contract EulerSwap is IEulerSwap, EVCUtil {
240244

241245
/// @dev EulerSwap curve definition
242246
/// Pre-conditions: x <= x0, 1 <= {px,py} <= 1e36, {x0,y0} <= type(uint112).max, c <= 1e18
243-
function f(uint256 x, uint256 px, uint256 py, uint256 x0, uint256 y0, uint256 c) public pure returns (uint256) {
244-
return y0 + (Math.mulDiv(px * (x0 - x), c * x + (1e18 - c) * x0, x * 1e18, Math.Rounding.Ceil) + (py - 1)) / py;
247+
function f(uint256 x, uint256 px, uint256 py, uint256 x0, uint256 y0, uint256 c) internal pure returns (uint256) {
248+
unchecked {
249+
uint256 v = Math.mulDiv(px * (x0 - x), c * x + (1e18 - c) * x0, x * 1e18, Math.Rounding.Ceil);
250+
require(v <= type(uint248).max, Overflow());
251+
return y0 + (v + (py - 1)) / py;
252+
}
245253
}
246254
}

test/EulerSwapFactoryTest.t.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ contract EulerSwapFactoryTest is EulerSwapTestBase {
7979

8080
function testDeployWithBadFee() public {
8181
vm.prank(creator);
82-
vm.expectRevert(EulerSwap.BadFee.selector);
82+
vm.expectRevert(EulerSwap.BadParam.selector);
8383
eulerSwapFactory.deployPool(
8484
IEulerSwapFactory.DeployParams(
8585
address(eTST), address(eTST2), holder, 1e18, 1e18, 1e18, 0.4e18, 0.85e18, 50e18, 50e18

test/EulerSwapTest.t.sol

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,14 +176,17 @@ contract EulerSwapTest is EulerSwapTestBase {
176176
}
177177
}
178178

179+
/*
180+
// Make `f()` function public to run this test
179181
function test_fFuncOverflow(uint256 xt, uint256 px, uint256 py, uint256 x0, uint256 y0, uint256 c) public view {
180182
x0 = bound(x0, 1, type(uint112).max);
181183
y0 = bound(y0, 0, type(uint112).max);
182-
xt = bound(xt, 1 + x0 / 1e6, x0); // from 1 millionth of the reserves left up
184+
xt = bound(xt, 1 + x0 / 1e3, x0); // thousand-fold price movement
183185
px = bound(px, 1, 1e36);
184186
py = bound(py, 1, 1e36);
185187
c = bound(c, 1, 1e18);
186188
187189
eulerSwap.f(xt, px, py, x0, y0, c);
188190
}
191+
*/
189192
}

0 commit comments

Comments
 (0)