Skip to content

Commit da8f2ee

Browse files
Merge pull request #35 from euler-xyz/fInverse
add fInverse() function with tests to periphery
2 parents 0e0f10a + 7d59efa commit da8f2ee

File tree

6 files changed

+102
-2
lines changed

6 files changed

+102
-2
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@
1010
[submodule "lib/ethereum-vault-connector"]
1111
path = lib/ethereum-vault-connector
1212
url = https://github.com/euler-xyz/ethereum-vault-connector
13+
[submodule "lib/v4-core"]
14+
path = lib/v4-core
15+
url = https://github.com/Uniswap/v4-core

lib/v4-core

Submodule v4-core added at 80311e3

remappings.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ evc/=lib/ethereum-vault-connector/src/
33
evk/=lib/euler-vault-kit/src/
44
ethereum-vault-connector/=lib/ethereum-vault-connector/src/
55
evk-test/=lib/euler-vault-kit/test/
6-
permit2/=lib/euler-vault-kit/lib/permit2/
6+
permit2/=lib/euler-vault-kit/lib/permit2/
7+
@uniswap/v4-core/=lib/v4-core/src/

src/EulerSwapPeriphery.sol

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {IEVC} from "evc/interfaces/IEthereumVaultConnector.sol";
55
import {IEVault} from "evk/EVault/IEVault.sol";
66
import {IEulerSwapPeriphery} from "./interfaces/IEulerSwapPeriphery.sol";
77
import {IERC20, IEulerSwap, SafeERC20} from "./EulerSwap.sol";
8+
import {Math} from "openzeppelin-contracts/utils/math/Math.sol";
9+
import "@uniswap/v4-core/libraries/FullMath.sol";
810

911
contract EulerSwapPeriphery is IEulerSwapPeriphery {
1012
using SafeERC20 for IERC20;
@@ -188,4 +190,57 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
188190
else output = uint256(dy);
189191
}
190192
}
193+
194+
/**
195+
* @notice Computes the inverse of the `f()` function for the EulerSwap liquidity curve.
196+
* @dev Solves for `x` given `y` using the quadratic formula derived from the liquidity curve:
197+
* x = (-b + sqrt(b^2 + 4ac)) / 2a
198+
* Utilises Uniswap's FullMath to avoid overflow and ensures precision with upward rounding.
199+
*
200+
* @param y The y-coordinate input value (must be greater than `y0`).
201+
* @param px Price factor for the x-axis (scaled by 1e18, between 1e18 and 1e36).
202+
* @param py Price factor for the y-axis (scaled by 1e18, between 1e18 and 1e36).
203+
* @param x0 Reference x-value on the liquidity curve (≤ 2^112 - 1).
204+
* @param y0 Reference y-value on the liquidity curve (≤ 2^112 - 1).
205+
* @param c Curve parameter shaping liquidity concentration (scaled by 1e18, between 0 and 1e18).
206+
*
207+
* @return x The computed x-coordinate on the liquidity curve.
208+
*
209+
* @custom:precision Uses rounding up to maintain precision in all calculations.
210+
* @custom:safety FullMath handles potential overflow in the b^2 computation.
211+
* @custom:requirement Input `y` must be strictly greater than `y0`; otherwise, the function will revert.
212+
*/
213+
function fInverse(uint256 y, uint256 px, uint256 py, uint256 x0, uint256 y0, uint256 c)
214+
external
215+
pure
216+
returns (uint256)
217+
{
218+
// A component of the quadratic formula: a = 2 * c
219+
uint256 A = 2 * c;
220+
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);
223+
224+
// B^2 component, using FullMath for overflow safety
225+
uint256 absB = B < 0 ? uint256(-B) : uint256(B);
226+
uint256 squaredB = FullMath.mulDiv(absB, absB, 1e18) + (absB * absB % 1e18 == 0 ? 0 : 1);
227+
228+
// 4 * A * C component of the quadratic formula
229+
uint256 AC4 = Math.mulDiv(
230+
Math.mulDiv(4 * c, (1e18 - c), 1e18, Math.Rounding.Ceil),
231+
Math.mulDiv(x0, x0, 1e18, Math.Rounding.Ceil),
232+
1e18,
233+
Math.Rounding.Ceil
234+
);
235+
236+
// Discriminant: b^2 + 4ac, scaled up to maintain precision
237+
uint256 discriminant = (squaredB + AC4) * 1e18;
238+
239+
// Square root of the discriminant (rounded up)
240+
uint256 sqrt = Math.sqrt(discriminant);
241+
sqrt = (sqrt * sqrt < discriminant) ? sqrt + 1 : sqrt;
242+
243+
// Compute and return x = fInverse(y) using the quadratic formula
244+
return Math.mulDiv(uint256(int256(sqrt) - B), 1e18, A, Math.Rounding.Ceil);
245+
}
191246
}

test/EulerSwapPeriphery.t.sol

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
// SPDX-License-Identifier: GPL-2.0-or-later
22
pragma solidity ^0.8.24;
33

4-
import {EulerSwapTestBase, EulerSwap, EulerSwapPeriphery} from "./EulerSwapTestBase.t.sol";
4+
import {EulerSwapTestBase, EulerSwap, EulerSwapPeriphery, IEulerSwap} from "./EulerSwapTestBase.t.sol";
5+
import {EulerSwapHarness} from "./harness/EulerSwapHarness.sol";
56

67
contract EulerSwapPeripheryTest is EulerSwapTestBase {
78
EulerSwap public eulerSwap;
9+
EulerSwapHarness public eulerSwapHarness;
810

911
function setUp() public virtual override {
1012
super.setUp();
1113

1214
eulerSwap = createEulerSwap(50e18, 50e18, 0, 1e18, 1e18, 0.4e18, 0.85e18);
15+
16+
IEulerSwap.Params memory params = getEulerSwapParams(50e18, 50e18, 0.4e18);
17+
IEulerSwap.CurveParams memory curveParams =
18+
IEulerSwap.CurveParams({priceX: 1e18, priceY: 1e18, concentrationX: 0.85e18, concentrationY: 0.85e18});
19+
20+
eulerSwapHarness = new EulerSwapHarness(params, curveParams); // Use the mock EulerSwap contract with a public f() function
1321
}
1422

1523
function test_SwapExactIn() public {
@@ -69,4 +77,17 @@ contract EulerSwapPeripheryTest is EulerSwapTestBase {
6977
periphery.swapExactOut(address(eulerSwap), address(assetTST), address(assetTST2), amountOut * 2, amountIn);
7078
vm.stopPrank();
7179
}
80+
81+
function test_fInverseFuzz(uint256 x) public view {
82+
x = bound(x, 2, 50e18 - 1); // note that it fails if 1 used as minimum, not an issue since only used in periphery
83+
uint256 y = eulerSwapHarness.exposedF(x, 1e18, 1e18, 50e18, 50e18, 0.85e18);
84+
uint256 outX = periphery.fInverse(y, 1e18, 1e18, 50e18, 50e18, 0.85e18);
85+
86+
// Ensure x is within the expected range
87+
assertGe(outX, x); // Asserts xOut >= x
88+
assertLe(outX, x + 1); // Asserts xOut <= x + 1
89+
90+
// Alternative using assertApproxEqAbs for absolute difference within 1
91+
assertApproxEqAbs(x, outX, 1);
92+
}
7293
}

test/harness/EulerSwapHarness.sol

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.27;
3+
4+
import {EulerSwap, IEulerSwap} from "../../src/EulerSwap.sol";
5+
6+
contract EulerSwapHarness is EulerSwap {
7+
constructor(IEulerSwap.Params memory params, IEulerSwap.CurveParams memory curveParams)
8+
EulerSwap(params, curveParams)
9+
{}
10+
11+
/// @notice Exposes the internal f() function as a public function for testing
12+
function exposedF(uint256 x, uint256 px, uint256 py, uint256 x0, uint256 y0, uint256 c)
13+
external
14+
pure
15+
returns (uint256)
16+
{
17+
return f(x, px, py, x0, y0, c);
18+
}
19+
}

0 commit comments

Comments
 (0)