Skip to content

Commit 81cf6dc

Browse files
committed
Merge remote-tracking branch 'origin/eulerswap2-updates-fixes' into eulerswap2
2 parents 8bfcf3d + 0e0d946 commit 81cf6dc

File tree

9 files changed

+122
-32
lines changed

9 files changed

+122
-32
lines changed

docs/developer-guide.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ In order to challenge a pool to retrieve the validity bond, a challenger invokes
120120
* The swap is actually performed by taking the input tokens from the challenger. The challenger must've given appropriate token approval to the registry. In all cases, the funds will be returned to the challenger, meaning they can be sourced with a flash loan.
121121
* If this swap succeeds, the entire transaction is reverted (including the swap) and the challenge is rejected.
122122
* If the swap failed for any reason other than `E_AccountLiquidity()` or `HookError()` then the challenge is rejected. This check is necessary because some vaults can fail for other expected reasons, such as unpopulated pull oracles.
123-
* At this point, the challenge has succeeded. The validity bond is sent to the `recipient` address provided by the challenger, and the pool is unregistered.
123+
* At this point, the challenge has succeeded. The validity bond is sent to the `recipient` address provided by the challenger, and the pool is unregistered. The challenger should ensure `recipent` is an address that can access native tokens.
124124

125125

126126

src/EulerSwap.sol

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,29 +18,15 @@ contract EulerSwap is IEulerSwap, UniswapHook {
1818

1919
error AmountTooBig();
2020

21-
/// @notice Emitted upon EulerSwap instance creation or reconfiguration.
22-
event EulerSwapConfigured(DynamicParams dParams, InitialState initialState);
23-
/// @notice Emitted upon EulerSwap instance creation or reconfiguration.
24-
event EulerSwapManagerSet(address indexed manager, bool installed);
25-
2621
constructor(address evc_, address protocolFeeConfig_, address poolManager_, address managementImpl_)
2722
UniswapHook(evc_, protocolFeeConfig_, poolManager_)
2823
{
2924
managementImpl = managementImpl_;
3025
}
3126

32-
function delegateToManagementImpl() internal {
33-
(bool success, bytes memory result) = managementImpl.delegatecall(msg.data);
34-
if (!success) {
35-
assembly {
36-
revert(add(32, result), mload(result))
37-
}
38-
}
39-
}
40-
4127
/// @inheritdoc IEulerSwap
4228
function activate(DynamicParams calldata, InitialState calldata) external {
43-
delegateToManagementImpl();
29+
_delegateToManagementImpl();
4430

4531
// Uniswap hook activation
4632

@@ -49,12 +35,12 @@ contract EulerSwap is IEulerSwap, UniswapHook {
4935

5036
/// @inheritdoc IEulerSwap
5137
function setManager(address, bool) external {
52-
delegateToManagementImpl();
38+
_delegateToManagementImpl();
5339
}
5440

5541
/// @inheritdoc IEulerSwap
5642
function reconfigure(DynamicParams calldata, InitialState calldata) external {
57-
delegateToManagementImpl();
43+
_delegateToManagementImpl();
5844
}
5945

6046
/// @inheritdoc IEulerSwap
@@ -165,4 +151,13 @@ contract EulerSwap is IEulerSwap, UniswapHook {
165151

166152
SwapLib.finish(ctx);
167153
}
154+
155+
function _delegateToManagementImpl() internal {
156+
(bool success, bytes memory result) = managementImpl.delegatecall(msg.data);
157+
if (!success) {
158+
assembly {
159+
revert(add(32, result), mload(result))
160+
}
161+
}
162+
}
168163
}

src/EulerSwapManagement.sol

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
// SPDX-License-Identifier: BUSL-1.1
22
pragma solidity ^0.8.27;
33

4-
import {IERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
5-
6-
import {IEulerSwapCallee} from "./interfaces/IEulerSwapCallee.sol";
74
import {IEVC} from "evc/interfaces/IEthereumVaultConnector.sol";
85
import {IEVault} from "evk/EVault/IEVault.sol";
96

@@ -14,14 +11,17 @@ import {FundsLib} from "./libraries/FundsLib.sol";
1411
import {CurveLib} from "./libraries/CurveLib.sol";
1512
import {SwapLib} from "./libraries/SwapLib.sol";
1613

14+
/// @title EulerSwapManagement contract
15+
/// @custom:security-contact security@euler.xyz
16+
/// @author Euler Labs (https://www.eulerlabs.com/)
1717
contract EulerSwapManagement is EulerSwapBase {
1818
error Unauthorized();
1919
error AlreadyActivated();
2020
error BadStaticParam();
2121
error BadDynamicParam();
22-
error AmountTooBig();
2322
error AssetsOutOfOrderOrEqual();
2423
error InvalidAssets();
24+
error BadFeeRecipient();
2525

2626
/// @notice Emitted upon EulerSwap instance creation or reconfiguration.
2727
event EulerSwapConfigured(IEulerSwap.DynamicParams dParams, IEulerSwap.InitialState initialState);
@@ -65,7 +65,7 @@ contract EulerSwapManagement is EulerSwapBase {
6565
IEulerSwap.StaticParams memory sParams = CtxLib.getStaticParams();
6666

6767
require(s.status == 0, AlreadyActivated());
68-
s.status = 1;
68+
s.status = 2; // Keep pool locked during activation
6969

7070
// Static parameters
7171

@@ -88,6 +88,11 @@ contract EulerSwapManagement is EulerSwapBase {
8888

8989
require(sParams.eulerAccount != sParams.feeRecipient, BadStaticParam()); // set feeRecipient to 0 instead
9090

91+
if (sParams.feeRecipient != address(0)) {
92+
address owner = evc.getAccountOwner(sParams.feeRecipient);
93+
require(owner == sParams.feeRecipient || owner == address(0), BadFeeRecipient());
94+
}
95+
9196
// Dynamic parameters
9297

9398
if (initialState.reserve0 != 0) {
@@ -127,6 +132,8 @@ contract EulerSwapManagement is EulerSwapBase {
127132
) {
128133
IEVC(evc).enableCollateral(sParams.eulerAccount, sParams.supplyVault1);
129134
}
135+
136+
s.status = 1; // unlock pool
130137
}
131138

132139
function setManager(address manager, bool installed) external nonReentrant {

src/EulerSwapProtocolFeeConfig.sol

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,24 @@ contract EulerSwapProtocolFeeConfig is IEulerSwapProtocolFeeConfig, EVCUtil {
2929
mapping(address pool => Override) public overrides;
3030

3131
error Unauthorized();
32+
error InvalidAdminAddress();
3233
error InvalidProtocolFee();
34+
error InvalidProtocolFeeRecipient();
35+
36+
/// @notice Emitted when admin is set/changed
37+
event AdminUpdated(address indexed oldAdmin, address indexed newAdmin);
38+
/// @notice Emitted when the default configuration is changed
39+
event DefaultUpdated(address indexed oldRecipient, address indexed newRecipient, uint64 oldFee, uint64 newFee);
40+
/// @notice Emitted when a per-pool override is created or changed
41+
event OverrideSet(address indexed pool, address indexed recipient, uint64 fee);
42+
/// @notice Emitted when a per-pool override is removed (and thus falls back to the default)
43+
event OverrideRemoved(address indexed pool);
3344

3445
constructor(address evc, address admin_) EVCUtil(evc) {
46+
_validateAdminAddress(admin_);
47+
48+
emit AdminUpdated(address(0), admin_);
49+
3550
admin = admin_;
3651
}
3752

@@ -46,12 +61,19 @@ contract EulerSwapProtocolFeeConfig is IEulerSwapProtocolFeeConfig, EVCUtil {
4661

4762
/// @inheritdoc IEulerSwapProtocolFeeConfig
4863
function setAdmin(address newAdmin) external onlyAdmin {
64+
_validateAdminAddress(newAdmin);
65+
66+
emit AdminUpdated(admin, newAdmin);
67+
4968
admin = newAdmin;
5069
}
5170

5271
/// @inheritdoc IEulerSwapProtocolFeeConfig
5372
function setDefault(address recipient, uint64 fee) external onlyAdmin {
5473
require(fee <= MAX_PROTOCOL_FEE, InvalidProtocolFee());
74+
require(fee == 0 || recipient != address(0), InvalidProtocolFeeRecipient());
75+
76+
emit DefaultUpdated(defaultRecipient, recipient, defaultFee, fee);
5577

5678
defaultRecipient = recipient;
5779
defaultFee = fee;
@@ -61,11 +83,15 @@ contract EulerSwapProtocolFeeConfig is IEulerSwapProtocolFeeConfig, EVCUtil {
6183
function setOverride(address pool, address recipient, uint64 fee) external onlyAdmin {
6284
require(fee <= MAX_PROTOCOL_FEE, InvalidProtocolFee());
6385

86+
emit OverrideSet(pool, recipient, fee);
87+
6488
overrides[pool] = Override({exists: true, recipient: recipient, fee: fee});
6589
}
6690

6791
/// @inheritdoc IEulerSwapProtocolFeeConfig
6892
function removeOverride(address pool) external onlyAdmin {
93+
emit OverrideRemoved(pool);
94+
6995
delete overrides[pool];
7096
}
7197

@@ -83,4 +109,10 @@ contract EulerSwapProtocolFeeConfig is IEulerSwapProtocolFeeConfig, EVCUtil {
83109
fee = defaultFee;
84110
}
85111
}
112+
113+
/// @dev Ensures the admin is not a known sub-account, since they are not allowed
114+
function _validateAdminAddress(address addr) internal view {
115+
address owner = evc.getAccountOwner(addr);
116+
require(owner == addr || owner == address(0), InvalidAdminAddress());
117+
}
86118
}

src/UniswapHook.sol

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,7 @@ import {IEVault} from "evk/EVault/IEVault.sol";
1616

1717
import {EulerSwapBase} from "./EulerSwapBase.sol";
1818
import {IEulerSwap} from "./interfaces/IEulerSwap.sol";
19-
import {CtxLib} from "./libraries/CtxLib.sol";
2019
import {QuoteLib} from "./libraries/QuoteLib.sol";
21-
import {CurveLib} from "./libraries/CurveLib.sol";
22-
import {FundsLib} from "./libraries/FundsLib.sol";
2320
import {SwapLib} from "./libraries/SwapLib.sol";
2421

2522
abstract contract UniswapHook is EulerSwapBase, BaseHook {

src/interfaces/IEulerSwapCallee.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,9 @@ interface IEulerSwapCallee {
66
/// is invoked on the `to` address, allowing flash-swaps (withdrawing output before
77
/// sending input.
88
/// @dev This callback mechanism is designed to be as similar as possible to Uniswap2.
9+
/// @param sender The address that originated the swap
10+
/// @param amount0 The requested output amount of token0
11+
/// @param amount1 The requested output amount of token1
12+
/// @param data Opaque callback data passed by swapper
913
function eulerSwapCall(address sender, uint256 amount0, uint256 amount1, bytes calldata data) external;
1014
}

src/libraries/FundsLib.sol

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"
77
import {IEVC} from "evc/interfaces/IEthereumVaultConnector.sol";
88
import {IEVault, IBorrowing, IERC4626, IRiskManager} from "evk/EVault/IEVault.sol";
99

10-
import {IEulerSwap} from "../interfaces/IEulerSwap.sol";
11-
1210
library FundsLib {
1311
using SafeERC20 for IERC20;
1412

@@ -74,7 +72,7 @@ library FundsLib {
7472
/// @param amount The total amount to deposit
7573
/// @return The amount of assets successfully deposited (may be less than requested)
7674
/// @dev This function attempts to deposit assets into the specified vault.
77-
/// @dev If the deposit fails with E_ZeroShares error, it safely returns 0 (this happens with very small amounts).
75+
/// @dev If the deposit fails with E_ZeroShares or ZeroShares error, it safely returns 0 (this happens with very small amounts).
7876
/// @dev After successful deposit, if the user has any outstanding controller-enabled debt, it attempts to repay it.
7977
/// @dev If all debt is repaid, the controller is automatically disabled to reduce gas costs in future operations.
8078
function depositAssets(address evc, address eulerAccount, address supplyVault, address borrowVault, uint256 amount)

test/EulerSwapTestBase.t.sol

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ contract EulerSwapTestBase is EVaultTestBase {
4242
address installedOperator;
4343
bool expectInsufficientValidityBondRevert = false;
4444
bool expectAccountLiquidityRevert = false;
45+
bool expectBadFeeRecipient = false;
4546

4647
error E_AccountLiquidity();
4748

@@ -172,13 +173,14 @@ contract EulerSwapTestBase is EVaultTestBase {
172173

173174
vm.prank(holder);
174175
if (expectAccountLiquidityRevert) vm.expectRevert(E_AccountLiquidity.selector);
176+
if (expectBadFeeRecipient) vm.expectRevert(EulerSwapManagement.BadFeeRecipient.selector);
175177
bytes memory result = IEVC(evc).call(
176178
address(eulerSwapFactory),
177179
sParams.eulerAccount,
178180
0,
179181
abi.encodeCall(EulerSwapFactory.deployPool, (sParams, dParams, initialState, salt))
180182
);
181-
if (expectAccountLiquidityRevert) return EulerSwap(address(0)); // Just to return to test
183+
if (expectAccountLiquidityRevert || expectBadFeeRecipient) return EulerSwap(address(0)); // Just to return to test
182184
EulerSwap eulerSwap = EulerSwap(abi.decode(result, (address)));
183185

184186
vm.prank(holder);

test/Fees.t.sol

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,23 @@ contract FeesTest is EulerSwapTestBase {
163163
assertEq(assetTST.balanceOf(address(54321)), amountIn - amountInNoFees);
164164
}
165165

166+
function test_altFeeRecipient_subAccount() public {
167+
address recipient = makeAddr("feeRecipient");
168+
169+
// Register owner with EVC
170+
vm.prank(recipient);
171+
evc.enableCollateral(recipient, address(0));
172+
173+
address subAccount = address(uint160(recipient) ^ 1);
174+
175+
(IEulerSwap.StaticParams memory sParams, IEulerSwap.DynamicParams memory dParams) =
176+
getEulerSwapParams(60e18, 60e18, 1e18, 1e18, 0.9e18, 0.9e18, 0.01e18, subAccount);
177+
IEulerSwap.InitialState memory initialState = IEulerSwap.InitialState({reserve0: 60e18, reserve1: 60e18});
178+
179+
expectBadFeeRecipient = true;
180+
eulerSwap = createEulerSwapFull(sParams, dParams, initialState);
181+
}
182+
166183
function test_fees_protocolFees_swap() public {
167184
uint256 fee = 0.05e18;
168185
uint256 protocolFee = 0.1e18;
@@ -221,6 +238,10 @@ contract FeesTest is EulerSwapTestBase {
221238
vm.expectRevert(EulerSwapProtocolFeeConfig.InvalidProtocolFee.selector);
222239
protocolFeeConfig.setDefault(address(8888), 0.15000001e18);
223240

241+
vm.prank(protocolFeeAdmin);
242+
vm.expectRevert(EulerSwapProtocolFeeConfig.InvalidProtocolFeeRecipient.selector);
243+
protocolFeeConfig.setDefault(address(0), 0.1e18);
244+
224245
// Set a default
225246

226247
vm.prank(protocolFeeAdmin);
@@ -234,6 +255,8 @@ contract FeesTest is EulerSwapTestBase {
234255

235256
// Override
236257

258+
vm.expectEmit(true, true, true, true);
259+
emit EulerSwapProtocolFeeConfig.OverrideSet(address(eulerSwap), address(9999), 0.07e18);
237260
vm.prank(protocolFeeAdmin);
238261
protocolFeeConfig.setOverride(address(eulerSwap), address(9999), 0.07e18);
239262

@@ -267,6 +290,8 @@ contract FeesTest is EulerSwapTestBase {
267290

268291
// Remove override
269292

293+
vm.expectEmit(true, true, true, true);
294+
emit EulerSwapProtocolFeeConfig.OverrideRemoved(address(eulerSwap));
270295
vm.prank(protocolFeeAdmin);
271296
protocolFeeConfig.removeOverride(address(eulerSwap));
272297

@@ -275,20 +300,50 @@ contract FeesTest is EulerSwapTestBase {
275300
assertEq(recipient, address(7777));
276301
assertEq(fee, 0.12e18);
277302
}
303+
304+
// Change override back to 0s
305+
306+
vm.prank(protocolFeeAdmin);
307+
protocolFeeConfig.setDefault(address(0), 0);
308+
309+
{
310+
(address recipient, uint64 fee) = protocolFeeConfig.getProtocolFee(address(eulerSwap));
311+
assertEq(recipient, address(0));
312+
assertEq(fee, 0);
313+
}
278314
}
279315

280316
function test_fees_protocolFees_setAdmin() public {
281-
assertEq(protocolFeeConfig.admin(), protocolFeeAdmin);
317+
// Register owner with EVC
318+
evc.enableCollateral(address(this), address(0));
319+
320+
address origAdmin = protocolFeeConfig.admin();
321+
assertEq(origAdmin, protocolFeeAdmin);
282322

283323
vm.expectRevert(EulerSwapProtocolFeeConfig.Unauthorized.selector);
284324
protocolFeeConfig.setDefault(address(8888), 0.1e18);
285325

326+
// Can't set a subaccount
327+
vm.expectRevert(EulerSwapProtocolFeeConfig.InvalidAdminAddress.selector);
328+
vm.prank(protocolFeeAdmin);
329+
protocolFeeConfig.setAdmin(address(uint160(address(this)) ^ 1));
330+
331+
vm.expectEmit(true, true, true, true);
332+
emit EulerSwapProtocolFeeConfig.AdminUpdated(origAdmin, address(this));
286333
vm.prank(protocolFeeAdmin);
287334
protocolFeeConfig.setAdmin(address(this));
288335

289336
assertEq(protocolFeeConfig.admin(), address(this));
337+
}
290338

291-
protocolFeeConfig.setDefault(address(8888), 0.1e18);
339+
function test_fees_protocolFees_defaults() public {
340+
address origFefaultRecipient = protocolFeeConfig.defaultRecipient();
341+
uint64 origDefaultFee = protocolFeeConfig.defaultFee();
342+
343+
vm.expectEmit(true, true, true, true);
344+
emit EulerSwapProtocolFeeConfig.DefaultUpdated(origFefaultRecipient, address(7654), origDefaultFee, 0.071e18);
345+
vm.prank(protocolFeeAdmin);
346+
protocolFeeConfig.setDefault(address(7654), 0.071e18);
292347
}
293348

294349
function test_fuzzFeeRounding(uint256 amount, uint256 fee) public pure {

0 commit comments

Comments
 (0)