Skip to content

Commit b948f40

Browse files
authored
Merge pull request #99 from euler-xyz/emit-swap-event-in-hook
Emit swap event in hook
2 parents f447c3f + 3d720be commit b948f40

File tree

5 files changed

+173
-13
lines changed

5 files changed

+173
-13
lines changed

src/EulerSwap.sol

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {IEVault} from "evk/EVault/IEVault.sol";
99

1010
import {IEulerSwap} from "./interfaces/IEulerSwap.sol";
1111
import {UniswapHook} from "./UniswapHook.sol";
12+
import "./Events.sol";
1213
import {CtxLib} from "./libraries/CtxLib.sol";
1314
import {FundsLib} from "./libraries/FundsLib.sol";
1415
import {CurveLib} from "./libraries/CurveLib.sol";
@@ -17,18 +18,6 @@ import {QuoteLib} from "./libraries/QuoteLib.sol";
1718
contract EulerSwap is IEulerSwap, EVCUtil, UniswapHook {
1819
bytes32 public constant curve = bytes32("EulerSwap v1");
1920

20-
event EulerSwapActivated(address indexed asset0, address indexed asset1);
21-
event Swap(
22-
address indexed sender,
23-
uint256 amount0In,
24-
uint256 amount1In,
25-
uint256 amount0Out,
26-
uint256 amount1Out,
27-
uint112 reserve0,
28-
uint112 reserve1,
29-
address indexed to
30-
);
31-
3221
error Locked();
3322
error AlreadyActivated();
3423
error BadParam();

src/Events.sol

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
pragma solidity ^0.8.27;
3+
4+
/// @notice Emitted upon EulerSwap instance creation.
5+
/// * `asset0` and `asset1` are the underlying assets of the vaults.
6+
/// They are always in lexical order: `asset0 < asset1`.
7+
event EulerSwapActivated(address indexed asset0, address indexed asset1);
8+
9+
/// @notice Emitted after every swap.
10+
/// * `sender` is the initiator of the swap, or the Router when invoked via hook.
11+
/// * `amount0In` and `amount1In` are after fees have been subtracted.
12+
/// * `reserve0` and `reserve1` are the pool's new reserves (after the swap).
13+
/// * `to` is the specified recipient of the funds, or the PoolManager when invoked via hook.
14+
event Swap(
15+
address indexed sender,
16+
uint256 amount0In,
17+
uint256 amount1In,
18+
uint256 amount0Out,
19+
uint256 amount1Out,
20+
uint112 reserve0,
21+
uint112 reserve1,
22+
address indexed to
23+
);

src/UniswapHook.sol

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
import {IEVault} from "evk/EVault/IEVault.sol";
1616

1717
import {IEulerSwap} from "./interfaces/IEulerSwap.sol";
18+
import "./Events.sol";
1819
import {CtxLib} from "./libraries/CtxLib.sol";
1920
import {QuoteLib} from "./libraries/QuoteLib.sol";
2021
import {CurveLib} from "./libraries/CurveLib.sol";
@@ -79,7 +80,7 @@ contract UniswapHook is BaseHook {
7980
}
8081
}
8182

82-
function _beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata)
83+
function _beforeSwap(address sender, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata)
8384
internal
8485
override
8586
nonReentrantHook
@@ -135,6 +136,12 @@ contract UniswapHook is BaseHook {
135136

136137
s.reserve0 = uint112(newReserve0);
137138
s.reserve1 = uint112(newReserve1);
139+
140+
if (params.zeroForOne) {
141+
emit Swap(sender, amountInWithoutFee, 0, 0, amountOut, s.reserve0, s.reserve1, msg.sender);
142+
} else {
143+
emit Swap(sender, 0, amountInWithoutFee, amountOut, 0, s.reserve0, s.reserve1, msg.sender);
144+
}
138145
}
139146

140147
return (BaseHook.beforeSwap.selector, returnDelta, 0);

test/Events.t.sol

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
pragma solidity ^0.8.24;
3+
4+
import {IEVault, IEulerSwap, EulerSwapTestBase, EulerSwap, TestERC20} from "./EulerSwapTestBase.t.sol";
5+
import "../src/Events.sol";
6+
7+
contract Events is EulerSwapTestBase {
8+
EulerSwap public eulerSwap;
9+
10+
function setUp() public virtual override {
11+
super.setUp();
12+
13+
uint256 myFee = 0.015e18;
14+
eulerSwap = createEulerSwap(60e18, 60e18, myFee, 1e18, 1e18, 0.4e18, 0.85e18);
15+
}
16+
17+
function test_events_exactInNormal() public {
18+
uint256 amountIn = 1e18;
19+
uint256 amountOut =
20+
periphery.quoteExactInput(address(eulerSwap), address(assetTST), address(assetTST2), amountIn);
21+
assertApproxEqAbs(amountOut, 0.9825e18, 0.0001e18);
22+
23+
assetTST.mint(address(this), amountIn);
24+
25+
assetTST.transfer(address(eulerSwap), amountIn);
26+
27+
{
28+
uint256 amountInWithoutFee = amountIn - (amountIn * eulerSwap.getParams().fee / 1e18);
29+
(uint112 r0, uint112 r1,) = eulerSwap.getReserves();
30+
vm.expectEmit(true, true, true, true);
31+
emit Swap(
32+
address(this),
33+
amountInWithoutFee,
34+
0,
35+
0,
36+
amountOut,
37+
r0 + uint112(amountInWithoutFee),
38+
r1 - uint112(amountOut),
39+
address(1234)
40+
);
41+
}
42+
43+
eulerSwap.swap(0, amountOut, address(1234), "");
44+
45+
assertEq(assetTST2.balanceOf(address(1234)), amountOut);
46+
}
47+
48+
function test_events_exactInReverse() public {
49+
uint256 amountIn = 1e18;
50+
uint256 amountOut =
51+
periphery.quoteExactInput(address(eulerSwap), address(assetTST2), address(assetTST), amountIn);
52+
assertApproxEqAbs(amountOut, 0.9753e18, 0.0001e18);
53+
54+
assetTST2.mint(address(this), amountIn);
55+
56+
assetTST2.transfer(address(eulerSwap), amountIn);
57+
58+
{
59+
uint256 amountInWithoutFee = amountIn - (amountIn * eulerSwap.getParams().fee / 1e18);
60+
(uint112 r0, uint112 r1,) = eulerSwap.getReserves();
61+
vm.expectEmit(true, true, true, true);
62+
emit Swap(
63+
address(this),
64+
0,
65+
amountInWithoutFee,
66+
amountOut,
67+
0,
68+
r0 - uint112(amountOut),
69+
r1 + uint112(amountInWithoutFee),
70+
address(this)
71+
);
72+
}
73+
74+
eulerSwap.swap(amountOut, 0, address(this), "");
75+
76+
assertEq(assetTST.balanceOf(address(this)), amountOut);
77+
}
78+
}

test/HookFees.t.sol

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol
1313
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
1414
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
1515
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
16+
import "../src/Events.sol";
1617

1718
contract HookFeesTest is EulerSwapTestBase {
1819
using StateLibrary for IPoolManager;
@@ -59,6 +60,18 @@ contract HookFeesTest is EulerSwapTestBase {
5960
vm.startPrank(anyone);
6061
assetTST.approve(address(minimalRouter), amountIn);
6162

63+
vm.expectEmit(true, true, true, true);
64+
emit Swap(
65+
address(minimalRouter),
66+
amountInWithoutFee,
67+
0,
68+
0,
69+
amountOut,
70+
r0 + uint112(amountInWithoutFee),
71+
r1 - uint112(amountOut),
72+
address(poolManager)
73+
);
74+
6275
bool zeroForOne = address(assetTST) < address(assetTST2);
6376
BalanceDelta result = minimalRouter.swap(eulerSwap.poolKey(), zeroForOne, amountIn, 0, "");
6477
vm.stopPrank();
@@ -83,6 +96,56 @@ contract HookFeesTest is EulerSwapTestBase {
8396
assertGt(getHolderNAV(), origNav + int256(amountIn - amountInWithoutFee));
8497
}
8598

99+
function test_SwapExactIn_withLpFeeReverse() public {
100+
int256 origNav = getHolderNAV();
101+
(uint112 r0, uint112 r1,) = eulerSwap.getReserves();
102+
103+
uint256 amountIn = 1e18;
104+
uint256 amountInWithoutFee = amountIn - (amountIn * eulerSwap.getParams().fee / 1e18);
105+
uint256 amountOut =
106+
periphery.quoteExactInput(address(eulerSwap), address(assetTST2), address(assetTST), amountIn);
107+
108+
assetTST2.mint(anyone, amountIn);
109+
110+
vm.startPrank(anyone);
111+
assetTST2.approve(address(minimalRouter), amountIn);
112+
113+
vm.expectEmit(true, true, true, true);
114+
emit Swap(
115+
address(minimalRouter),
116+
0,
117+
amountInWithoutFee,
118+
amountOut,
119+
0,
120+
r0 - uint112(amountOut),
121+
r1 + uint112(amountInWithoutFee),
122+
address(poolManager)
123+
);
124+
125+
bool zeroForOne = address(assetTST) < address(assetTST2);
126+
BalanceDelta result = minimalRouter.swap(eulerSwap.poolKey(), !zeroForOne, amountIn, 0, "");
127+
vm.stopPrank();
128+
129+
assertEq(assetTST2.balanceOf(anyone), 0);
130+
assertEq(assetTST.balanceOf(anyone), amountOut);
131+
132+
assertEq(!zeroForOne ? uint256(-int256(result.amount0())) : uint256(-int256(result.amount1())), amountIn);
133+
assertEq(!zeroForOne ? uint256(int256(result.amount1())) : uint256(int256(result.amount0())), amountOut);
134+
135+
// assert fees were not added to the reserves
136+
(uint112 r0New, uint112 r1New,) = eulerSwap.getReserves();
137+
if (!zeroForOne) {
138+
assertEq(r0New, r0 + amountInWithoutFee);
139+
assertEq(r1New, r1 - amountOut);
140+
} else {
141+
// oneForZero, so the curve received asset1
142+
assertEq(r0New, r0 - amountOut);
143+
assertEq(r1New, r1 + amountInWithoutFee);
144+
}
145+
146+
assertGt(getHolderNAV(), origNav + int256(amountIn - amountInWithoutFee));
147+
}
148+
86149
function test_SwapExactOut_withLpFee() public {
87150
int256 origNav = getHolderNAV();
88151
(uint112 r0, uint112 r1,) = eulerSwap.getReserves();

0 commit comments

Comments
 (0)