Skip to content

Commit b2285c8

Browse files
authored
Merge pull request #50 from euler-xyz/v4-computeQuote
V4 compute quote
2 parents 128d1cf + 4766454 commit b2285c8

File tree

2 files changed

+134
-4
lines changed

2 files changed

+134
-4
lines changed

src/EulerSwapHook.sol

Lines changed: 128 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,16 @@ contract EulerSwapHook is EulerSwap, BaseHook {
5555
params.zeroForOne ? (key.currency0, key.currency1) : (key.currency1, key.currency0);
5656
bool isExactInput = params.amountSpecified < 0;
5757

58-
// TODO: compute the open side of the trade, using computeQuote() ?
5958
uint256 amountIn;
6059
uint256 amountOut;
61-
// uint256 amountIn = isExactInput ? uint256(-params.amountSpecified) : computeQuote(..., false);
62-
// uint256 amountOut = isExactInput ? computeQuote(..., true) : uint256(params.amountSpecified);
60+
61+
if (isExactInput) {
62+
amountIn = uint256(-params.amountSpecified);
63+
amountOut = computeQuote(params.zeroForOne, uint256(-params.amountSpecified), true);
64+
} else {
65+
amountIn = computeQuote(params.zeroForOne, uint256(params.amountSpecified), false);
66+
amountOut = uint256(params.amountSpecified);
67+
}
6368

6469
// take the input token, from the PoolManager to the Euler vault
6570
// the debt will be paid by the swapper via the swap router
@@ -100,4 +105,124 @@ contract EulerSwapHook is EulerSwap, BaseHook {
100105
// TODO: fix salt mining & verification for the hook
101106
function getHookPermissions() public pure override returns (Hooks.Permissions memory) {}
102107
function validateHookAddress(BaseHook) internal pure override {}
108+
109+
error SwapLimitExceeded();
110+
error OperatorNotInstalled();
111+
112+
function computeQuote(bool asset0IsInput, uint256 amount, bool exactIn) internal view returns (uint256) {
113+
require(evc.isAccountOperatorAuthorized(eulerAccount, address(this)), OperatorNotInstalled());
114+
require(amount <= type(uint112).max, SwapLimitExceeded());
115+
116+
// exactIn: decrease received amountIn, rounding down
117+
if (exactIn) amount = amount * feeMultiplier / 1e18;
118+
119+
(uint256 inLimit, uint256 outLimit) = calcLimits(asset0IsInput);
120+
121+
uint256 quote = binarySearch(amount, exactIn, asset0IsInput);
122+
123+
if (exactIn) {
124+
// if `exactIn`, `quote` is the amount of assets to buy from the AMM
125+
require(amount <= inLimit && quote <= outLimit, SwapLimitExceeded());
126+
} else {
127+
// if `!exactIn`, `amount` is the amount of assets to buy from the AMM
128+
require(amount <= outLimit && quote <= inLimit, SwapLimitExceeded());
129+
}
130+
131+
// exactOut: increase required quote(amountIn), rounding up
132+
if (!exactIn) quote = (quote * 1e18 + (feeMultiplier - 1)) / feeMultiplier;
133+
134+
return quote;
135+
}
136+
137+
function binarySearch(uint256 amount, bool exactIn, bool asset0IsInput) internal view returns (uint256 output) {
138+
int256 dx;
139+
int256 dy;
140+
141+
if (exactIn) {
142+
if (asset0IsInput) dx = int256(amount);
143+
else dy = int256(amount);
144+
} else {
145+
if (asset0IsInput) dy = -int256(amount);
146+
else dx = -int256(amount);
147+
}
148+
149+
unchecked {
150+
int256 reserve0New = int256(uint256(reserve0)) + dx;
151+
int256 reserve1New = int256(uint256(reserve1)) + dy;
152+
require(reserve0New > 0 && reserve1New > 0, SwapLimitExceeded());
153+
154+
uint256 low;
155+
uint256 high = type(uint112).max;
156+
157+
while (low < high) {
158+
uint256 mid = (low + high) / 2;
159+
require(mid > 0, SwapLimitExceeded());
160+
(uint256 a, uint256 b) = dy == 0 ? (uint256(reserve0New), mid) : (mid, uint256(reserve1New));
161+
if (verify(a, b)) {
162+
high = mid;
163+
} else {
164+
low = mid + 1;
165+
}
166+
}
167+
168+
require(high < type(uint112).max, SwapLimitExceeded()); // at least one point verified
169+
170+
if (dx != 0) dy = int256(low) - reserve1New;
171+
else dx = int256(low) - reserve0New;
172+
}
173+
174+
if (exactIn) {
175+
if (asset0IsInput) output = uint256(-dy);
176+
else output = uint256(-dx);
177+
} else {
178+
if (asset0IsInput) output = dx >= 0 ? uint256(dx) : 0;
179+
else output = dy >= 0 ? uint256(dy) : 0;
180+
}
181+
}
182+
183+
function calcLimits(bool asset0IsInput) internal view returns (uint256, uint256) {
184+
uint256 inLimit = type(uint112).max;
185+
uint256 outLimit = type(uint112).max;
186+
187+
(IEVault vault0, IEVault vault1) = (IEVault(vault0), IEVault(vault1));
188+
// Supply caps on input
189+
{
190+
IEVault vault = (asset0IsInput ? vault0 : vault1);
191+
uint256 maxDeposit = vault.debtOf(eulerAccount) + vault.maxDeposit(eulerAccount);
192+
if (maxDeposit < inLimit) inLimit = maxDeposit;
193+
}
194+
195+
// Remaining reserves of output
196+
{
197+
uint112 reserveLimit = asset0IsInput ? reserve1 : reserve0;
198+
if (reserveLimit < outLimit) outLimit = reserveLimit;
199+
}
200+
201+
// Remaining cash and borrow caps in output
202+
{
203+
IEVault vault = (asset0IsInput ? vault1 : vault0);
204+
205+
uint256 cash = vault.cash();
206+
if (cash < outLimit) outLimit = cash;
207+
208+
(, uint16 borrowCap) = vault.caps();
209+
uint256 maxWithdraw = decodeCap(uint256(borrowCap));
210+
maxWithdraw = vault.totalBorrows() > maxWithdraw ? 0 : maxWithdraw - vault.totalBorrows();
211+
if (maxWithdraw > cash) maxWithdraw = cash;
212+
maxWithdraw += vault.convertToAssets(vault.balanceOf(eulerAccount));
213+
if (maxWithdraw < outLimit) outLimit = maxWithdraw;
214+
}
215+
216+
return (inLimit, outLimit);
217+
}
218+
219+
function decodeCap(uint256 amountCap) internal pure returns (uint256) {
220+
if (amountCap == 0) return type(uint256).max;
221+
222+
unchecked {
223+
// Cannot overflow because this is less than 2**256:
224+
// 10**(2**6 - 1) * (2**10 - 1) = 1.023e+66
225+
return 10 ** (amountCap & 63) * (amountCap >> 6) / 100;
226+
}
227+
}
103228
}

test/EulerSwapHook.swaps.t.sol

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,16 @@ contract EulerSwapHookTest is EulerSwapTestBase {
2929
swapRouter = new PoolSwapTest(poolManager);
3030

3131
eulerSwap = createEulerSwapHook(poolManager, 60e18, 60e18, 0, 1e18, 1e18, 0.4e18, 0.85e18);
32+
eulerSwap.activate();
3233

3334
// confirm pool was created
3435
assertFalse(eulerSwap.poolKey().currency1 == CurrencyLibrary.ADDRESS_ZERO);
3536
(uint160 sqrtPriceX96,,,) = poolManager.getSlot0(eulerSwap.poolKey().toId());
3637
assertNotEq(sqrtPriceX96, 0);
38+
39+
// Seed the poolManager with balance so that transient withdrawing before depositing succeeds
40+
assetTST.mint(address(poolManager), 1000e18);
41+
assetTST2.mint(address(poolManager), 1000e18);
3742
}
3843

3944
function test_SwapExactIn() public {
@@ -62,7 +67,7 @@ contract EulerSwapHookTest is EulerSwapTestBase {
6267
assetTST.mint(anyone, amountIn);
6368

6469
vm.startPrank(anyone);
65-
assetTST.approve(address(periphery), amountIn);
70+
assetTST.approve(address(swapRouter), amountIn);
6671
bool zeroForOne = address(assetTST) < address(assetTST2);
6772
_swap(eulerSwap.poolKey(), zeroForOne, false, amountOut);
6873
vm.stopPrank();

0 commit comments

Comments
 (0)