Skip to content

Commit 95e473f

Browse files
committed
verify that prepaid input does not have token liquidity issues
1 parent b2285c8 commit 95e473f

File tree

2 files changed

+93
-3
lines changed

2 files changed

+93
-3
lines changed

test/EulerSwapHook.swaps.t.sol

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {EulerSwapHook} from "../src/EulerSwapHook.sol";
88
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
99
import {IPoolManager, PoolManagerDeployer} from "./utils/PoolManagerDeployer.sol";
1010
import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol";
11+
import {MinimalRouter} from "./utils/MinimalRouter.sol";
1112
import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol";
1213
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
1314
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
@@ -19,6 +20,7 @@ contract EulerSwapHookTest is EulerSwapTestBase {
1920

2021
IPoolManager public poolManager;
2122
PoolSwapTest public swapRouter;
23+
MinimalRouter public minimalRouter;
2224

2325
PoolSwapTest.TestSettings public settings = PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false});
2426

@@ -27,6 +29,7 @@ contract EulerSwapHookTest is EulerSwapTestBase {
2729

2830
poolManager = PoolManagerDeployer.deploy(address(this));
2931
swapRouter = new PoolSwapTest(poolManager);
32+
minimalRouter = new MinimalRouter(poolManager);
3033

3134
eulerSwap = createEulerSwapHook(poolManager, 60e18, 60e18, 0, 1e18, 1e18, 0.4e18, 0.85e18);
3235
eulerSwap.activate();
@@ -42,23 +45,40 @@ contract EulerSwapHookTest is EulerSwapTestBase {
4245
}
4346

4447
function test_SwapExactIn() public {
45-
uint256 amountIn = 1e18;
48+
uint256 amountIn = 1001e18;
4649
uint256 amountOut =
4750
periphery.quoteExactInput(address(eulerSwap), address(assetTST), address(assetTST2), amountIn);
4851

4952
assetTST.mint(anyone, amountIn);
5053

5154
vm.startPrank(anyone);
52-
assetTST.approve(address(swapRouter), amountIn);
55+
assetTST.approve(address(minimalRouter), amountIn);
5356

5457
bool zeroForOne = address(assetTST) < address(assetTST2);
55-
_swap(eulerSwap.poolKey(), zeroForOne, true, amountIn);
58+
minimalRouter.swap(eulerSwap.poolKey(), zeroForOne, true, amountIn, "");
5659
vm.stopPrank();
5760

5861
assertEq(assetTST.balanceOf(anyone), 0);
5962
assertEq(assetTST2.balanceOf(anyone), amountOut);
6063
}
6164

65+
/// @dev swapping with an amount that exceeds PoolManager's ERC20 token balance will revert
66+
/// if the router does not pre-pay the input
67+
function test_swapExactIn_revertWithoutTokenLiquidity() public {
68+
uint256 amountIn = 1001e18; // input amount exceeds
69+
70+
assetTST.mint(anyone, amountIn);
71+
72+
vm.startPrank(anyone);
73+
assetTST.approve(address(swapRouter), amountIn);
74+
75+
bool zeroForOne = address(assetTST) < address(assetTST2);
76+
PoolKey memory poolKey = eulerSwap.poolKey();
77+
vm.expectRevert();
78+
_swap(poolKey, zeroForOne, true, amountIn);
79+
vm.stopPrank();
80+
}
81+
6282
function test_SwapExactOut() public {
6383
uint256 amountOut = 1e18;
6484
uint256 amountIn =

test/utils/MinimalRouter.sol

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.24;
3+
4+
import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol";
5+
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
6+
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
7+
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
8+
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
9+
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
10+
import {CurrencySettler} from "@uniswap/v4-core/test/utils/CurrencySettler.sol";
11+
import {SafeCallback} from "v4-periphery/src/base/SafeCallback.sol";
12+
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
13+
14+
contract MinimalRouter is SafeCallback {
15+
using CurrencySettler for Currency;
16+
17+
uint160 public constant MIN_PRICE_LIMIT = TickMath.MIN_SQRT_PRICE + 1;
18+
uint160 public constant MAX_PRICE_LIMIT = TickMath.MAX_SQRT_PRICE - 1;
19+
20+
constructor(IPoolManager _manager) SafeCallback(_manager) {}
21+
22+
function swap(PoolKey memory key, bool zeroForOne, bool exactInput, uint256 amount, bytes memory hookData)
23+
external
24+
payable
25+
returns (BalanceDelta delta)
26+
{
27+
delta = abi.decode(
28+
poolManager.unlock(abi.encode(msg.sender, key, zeroForOne, exactInput, amount, hookData)), (BalanceDelta)
29+
);
30+
31+
uint256 ethBalance = address(this).balance;
32+
if (ethBalance > 0) CurrencyLibrary.ADDRESS_ZERO.transfer(msg.sender, ethBalance);
33+
}
34+
35+
function _unlockCallback(bytes calldata data) internal override returns (bytes memory) {
36+
(address sender, PoolKey memory key, bool zeroForOne, bool exactInput, uint256 amount, bytes memory hookData) =
37+
abi.decode(data, (address, PoolKey, bool, bool, uint256, bytes));
38+
39+
// for exact input swaps, send the input first to avoid PoolManager token balance issues
40+
if (exactInput) {
41+
zeroForOne
42+
? key.currency0.settle(poolManager, sender, amount, false)
43+
: key.currency1.settle(poolManager, sender, amount, false);
44+
}
45+
46+
BalanceDelta delta = poolManager.swap(
47+
key,
48+
IPoolManager.SwapParams({
49+
zeroForOne: zeroForOne,
50+
amountSpecified: exactInput ? -int256(amount) : int256(amount),
51+
sqrtPriceLimitX96: zeroForOne ? MIN_PRICE_LIMIT : MAX_PRICE_LIMIT
52+
}),
53+
hookData
54+
);
55+
56+
if (!exactInput && delta.amount0() < 0) {
57+
key.currency0.settle(poolManager, sender, uint256(int256(-delta.amount0())), false);
58+
} else if (delta.amount0() > 0) {
59+
key.currency0.take(poolManager, sender, uint256(int256(delta.amount0())), false);
60+
}
61+
62+
if (!exactInput && delta.amount1() < 0) {
63+
key.currency1.settle(poolManager, sender, uint256(int256(-delta.amount1())), false);
64+
} else if (delta.amount1() > 0) {
65+
key.currency1.take(poolManager, sender, uint256(int256(delta.amount1())), false);
66+
}
67+
68+
return abi.encode(delta);
69+
}
70+
}

0 commit comments

Comments
 (0)