Skip to content

Commit f980ecf

Browse files
committed
simplify protocol fees
1 parent e93deae commit f980ecf

20 files changed

+258
-214
lines changed

remappings.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,3 @@ ethereum-vault-connector/=lib/ethereum-vault-connector/src/
55
evk-test/=lib/euler-vault-kit/test/
66
permit2/=lib/euler-vault-kit/lib/permit2/
77
@uniswap/v4-core/=lib/v4-periphery/lib/v4-core/
8-
solmate/=lib/v4-periphery/lib/v4-core/lib/solmate/src

script/DeployProtocol.s.sol

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {EulerSwapFactory} from "../src/EulerSwapFactory.sol";
66
import {EulerSwapRegistry} from "../src/EulerSwapRegistry.sol";
77
import {EulerSwapPeriphery} from "../src/EulerSwapPeriphery.sol";
88
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
9+
import {EulerSwapProtocolFeeConfig} from "../src/EulerSwapProtocolFeeConfig.sol";
910
import {EulerSwap} from "../src/EulerSwap.sol";
1011
import {EulerSwapManagement} from "../src/EulerSwapManagement.sol";
1112

@@ -22,16 +23,17 @@ contract DeployProtocol is ScriptUtil {
2223

2324
address evc = vm.parseJsonAddress(json, ".evc");
2425
address poolManager = vm.parseJsonAddress(json, ".poolManager");
25-
address feeOwner = vm.parseJsonAddress(json, ".feeOwner");
26-
address feeRecipientSetter = vm.parseJsonAddress(json, ".feeRecipientSetter");
26+
address protocolFeeAdmin = vm.parseJsonAddress(json, ".protocolFeeAdmin");
2727
address validVaultPerspective = vm.parseJsonAddress(json, ".validVaultPerspective");
2828
address curator = vm.parseJsonAddress(json, ".curator");
2929

3030
vm.startBroadcast(deployerAddress);
3131

32+
address eulerSwapProtocolFeeConfig = address(new EulerSwapProtocolFeeConfig(evc, protocolFeeAdmin));
3233
address eulerSwapManagementImpl = address(new EulerSwapManagement(evc));
33-
address eulerSwapImpl = address(new EulerSwap(evc, poolManager, eulerSwapManagementImpl));
34-
address eulerSwapFactory = address(new EulerSwapFactory(evc, eulerSwapImpl, feeOwner, feeRecipientSetter));
34+
address eulerSwapImpl =
35+
address(new EulerSwap(evc, eulerSwapProtocolFeeConfig, poolManager, eulerSwapManagementImpl));
36+
address eulerSwapFactory = address(new EulerSwapFactory(evc, eulerSwapImpl));
3537
new EulerSwapRegistry(evc, eulerSwapFactory, validVaultPerspective, curator);
3638
new EulerSwapPeriphery();
3739
vm.stopBroadcast();
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
{
22
"evc": "0x5301c7dD20bD945D2013b48ed0DEE3A284ca8989",
33
"poolManager": "0x498581fF718922c3f8e6A244956aF099B2652b2b",
4-
"feeOwner": "0x33C71422B3E20ef2472Bc9aa9252220CAeAF207e",
5-
"feeRecipientSetter": "0x33C71422B3E20ef2472Bc9aa9252220CAeAF207e",
4+
"protocolFeeAdmin": "0x33C71422B3E20ef2472Bc9aa9252220CAeAF207e",
65
"validVaultPerspective": "0xFEA8e8a4d7ab8C517c3790E49E92ED7E1166F651",
76
"curator": "0x33C71422B3E20ef2472Bc9aa9252220CAeAF207e"
87
}

src/EulerSwap.sol

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ contract EulerSwap is IEulerSwap, UniswapHook {
2323
/// @notice Emitted upon EulerSwap instance creation or reconfiguration.
2424
event EulerSwapManagerSet(address indexed manager, bool installed);
2525

26-
constructor(address evc_, address poolManager_, address managementImpl_) UniswapHook(evc_, poolManager_) {
26+
constructor(address evc_, address protocolFeeConfig_, address poolManager_, address managementImpl_)
27+
UniswapHook(evc_, protocolFeeConfig_, poolManager_)
28+
{
2729
managementImpl = managementImpl_;
2830
}
2931

@@ -140,7 +142,7 @@ contract EulerSwap is IEulerSwap, UniswapHook {
140142

141143
// Setup context
142144

143-
SwapLib.SwapContext memory ctx = SwapLib.init(address(evc), _msgSender(), to);
145+
SwapLib.SwapContext memory ctx = SwapLib.init(address(evc), protocolFeeConfig, _msgSender(), to);
144146
SwapLib.setAmountsOut(ctx, amount0Out, amount1Out);
145147
SwapLib.invokeBeforeSwapHook(ctx);
146148

src/EulerSwapFactory.sol

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@ import {IEulerSwapFactory, IEulerSwap} from "./interfaces/IEulerSwapFactory.sol"
55
import {EVCUtil} from "ethereum-vault-connector/utils/EVCUtil.sol";
66

77
import {EulerSwap} from "./EulerSwap.sol";
8-
import {ProtocolFee} from "./utils/ProtocolFee.sol";
98
import {MetaProxyDeployer} from "./utils/MetaProxyDeployer.sol";
109

1110
/// @title EulerSwapFactory contract
1211
/// @custom:security-contact [email protected]
1312
/// @author Euler Labs (https://www.eulerlabs.com/)
14-
contract EulerSwapFactory is IEulerSwapFactory, EVCUtil, ProtocolFee {
13+
contract EulerSwapFactory is IEulerSwapFactory, EVCUtil {
1514
/// @dev The EulerSwap code instance that will be proxied to
1615
address public immutable eulerSwapImpl;
1716

@@ -20,12 +19,8 @@ contract EulerSwapFactory is IEulerSwapFactory, EVCUtil, ProtocolFee {
2019

2120
error Unauthorized();
2221
error OperatorNotInstalled();
23-
error InvalidProtocolFee();
2422

25-
constructor(address evc, address eulerSwapImpl_, address feeOwner_, address feeRecipientSetter_)
26-
EVCUtil(evc)
27-
ProtocolFee(feeOwner_, feeRecipientSetter_)
28-
{
23+
constructor(address evc, address eulerSwapImpl_) EVCUtil(evc) {
2924
eulerSwapImpl = eulerSwapImpl_;
3025
}
3126

@@ -37,10 +32,6 @@ contract EulerSwapFactory is IEulerSwapFactory, EVCUtil, ProtocolFee {
3732
bytes32 salt
3833
) external returns (address) {
3934
require(_msgSender() == sParams.eulerAccount, Unauthorized());
40-
require(
41-
sParams.protocolFee == protocolFee && sParams.protocolFeeRecipient == protocolFeeRecipient,
42-
InvalidProtocolFee()
43-
);
4435

4536
EulerSwap pool = EulerSwap(MetaProxyDeployer.deployMetaProxy(eulerSwapImpl, abi.encode(sParams), salt));
4637
deployedPools[address(pool)] = true;
@@ -67,9 +58,4 @@ contract EulerSwapFactory is IEulerSwapFactory, EVCUtil, ProtocolFee {
6758
)
6859
);
6960
}
70-
71-
/// @dev For ProtocolFee access
72-
function _eulerSwapImpl() internal view override returns (address) {
73-
return eulerSwapImpl;
74-
}
7561
}

src/EulerSwapProtocolFeeConfig.sol

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.27;
3+
4+
import {EVCUtil} from "evc/utils/EVCUtil.sol";
5+
6+
/// @title EulerSwapProtocolFeeConfig contract
7+
/// @custom:security-contact [email protected]
8+
/// @author Euler Labs (https://www.eulerlabs.com/)
9+
contract EulerSwapProtocolFeeConfig is EVCUtil {
10+
/// @dev Protocol fee admin
11+
address public admin;
12+
13+
/// @dev Admin is not allowed to set a protocol fee larger than this
14+
uint64 public constant MAX_PROTOCOL_FEE = 0.15e18;
15+
16+
/// @dev Destination of collected protocol fees, unless overridden
17+
address public defaultRecipient;
18+
/// @dev Default protocol fee, 1e18-scale
19+
uint64 public defaultFee;
20+
21+
struct Override {
22+
bool exists;
23+
address recipient;
24+
uint64 fee;
25+
}
26+
27+
/// @dev EulerSwap-instance specific fee override
28+
mapping(address pool => Override) public overrides;
29+
30+
error Unauthorized();
31+
error InvalidProtocolFee();
32+
33+
constructor(address evc, address admin_) EVCUtil(evc) {
34+
admin = admin_;
35+
}
36+
37+
modifier onlyAdmin() {
38+
// Ensures that the caller is not an operator, controller, etc
39+
_authenticateCallerWithStandardContextState(true);
40+
41+
require(_msgSender() == admin, Unauthorized());
42+
43+
_;
44+
}
45+
46+
function setDefault(address recipient, uint64 fee) external onlyAdmin {
47+
require(fee <= MAX_PROTOCOL_FEE, InvalidProtocolFee());
48+
49+
defaultRecipient = recipient;
50+
defaultFee = fee;
51+
}
52+
53+
function setOverride(address pool, address recipient, uint64 fee) external onlyAdmin {
54+
require(fee <= MAX_PROTOCOL_FEE, InvalidProtocolFee());
55+
56+
overrides[pool] = Override({exists: true, recipient: recipient, fee: fee});
57+
}
58+
59+
function removeOverride(address pool) external onlyAdmin {
60+
delete overrides[pool];
61+
}
62+
63+
function getProtocolFee(address pool) external view returns (address recipient, uint64 fee) {
64+
Override memory o = overrides[pool];
65+
66+
if (o.exists) {
67+
recipient = o.recipient;
68+
fee = o.fee;
69+
70+
if (recipient == address(0)) recipient = defaultRecipient;
71+
} else {
72+
recipient = defaultRecipient;
73+
fee = defaultFee;
74+
}
75+
}
76+
}

src/UniswapHook.sol

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,16 @@ import {SwapLib} from "./libraries/SwapLib.sol";
2525
abstract contract UniswapHook is EulerSwapBase, BaseHook {
2626
using SafeCast for uint256;
2727

28+
address public immutable protocolFeeConfig;
29+
2830
PoolKey internal _poolKey;
2931

30-
constructor(address evc_, address _poolManager) EulerSwapBase(evc_) BaseHook(IPoolManager(_poolManager)) {}
32+
constructor(address evc_, address protocolFeeConfig_, address _poolManager)
33+
EulerSwapBase(evc_)
34+
BaseHook(IPoolManager(_poolManager))
35+
{
36+
protocolFeeConfig = protocolFeeConfig_;
37+
}
3138

3239
function activateHook(IEulerSwap.StaticParams memory sParams) internal nonReentrant {
3340
if (address(poolManager) == address(0)) return;
@@ -65,7 +72,7 @@ abstract contract UniswapHook is EulerSwapBase, BaseHook {
6572
nonReentrant
6673
returns (bytes4, BeforeSwapDelta, uint24)
6774
{
68-
SwapLib.SwapContext memory ctx = SwapLib.init(address(evc), sender, msg.sender);
75+
SwapLib.SwapContext memory ctx = SwapLib.init(address(evc), protocolFeeConfig, sender, msg.sender);
6976

7077
uint256 amountIn;
7178
uint256 amountOut;

src/interfaces/IEulerSwap.sol

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ interface IEulerSwap {
1010
address borrowVault1;
1111
address eulerAccount;
1212
address feeRecipient;
13-
address protocolFeeRecipient;
14-
uint64 protocolFee;
1513
}
1614

1715
/// @dev Reconfigurable pool parameters, loaded from storage.

src/libraries/CtxLib.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,11 @@ library CtxLib {
6060

6161
/// @dev Unpacks encoded Params from trailing calldata. Loosely based on
6262
/// the implementation from EIP-3448 (except length is hard-coded).
63-
/// 256 is the size of the StaticParams struct after ABI encoding.
63+
/// 192 is the size of the StaticParams struct after ABI encoding.
6464
function getStaticParams() internal pure returns (IEulerSwap.StaticParams memory p) {
65-
require(msg.data.length >= 256, InsufficientCalldata());
65+
require(msg.data.length >= 192, InsufficientCalldata());
6666
unchecked {
67-
return abi.decode(msg.data[msg.data.length - 256:], (IEulerSwap.StaticParams));
67+
return abi.decode(msg.data[msg.data.length - 192:], (IEulerSwap.StaticParams));
6868
}
6969
}
7070
}

src/libraries/SwapLib.sol

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {CtxLib} from "./CtxLib.sol";
99
import {CurveLib} from "./CurveLib.sol";
1010
import {FundsLib} from "./FundsLib.sol";
1111
import {QuoteLib} from "./QuoteLib.sol";
12+
import {EulerSwapProtocolFeeConfig} from "../EulerSwapProtocolFeeConfig.sol";
1213
import {IEulerSwap} from "../interfaces/IEulerSwap.sol";
1314
import "../interfaces/IEulerSwapHookTarget.sol";
1415

@@ -40,6 +41,7 @@ library SwapLib {
4041
struct SwapContext {
4142
// Populated by init
4243
address evc;
44+
address protocolFeeConfig;
4345
IEulerSwap.StaticParams sParams;
4446
IEulerSwap.DynamicParams dParams;
4547
address asset0;
@@ -56,8 +58,13 @@ library SwapLib {
5658
uint256 amount1In; // full minus fees
5759
}
5860

59-
function init(address evc, address sender, address to) internal view returns (SwapContext memory ctx) {
61+
function init(address evc, address protocolFeeConfig, address sender, address to)
62+
internal
63+
view
64+
returns (SwapContext memory ctx)
65+
{
6066
ctx.evc = evc;
67+
ctx.protocolFeeConfig = protocolFeeConfig;
6168
ctx.sParams = CtxLib.getStaticParams();
6269
ctx.dParams = CtxLib.getDynamicParams();
6370

@@ -170,14 +177,19 @@ library SwapLib {
170177

171178
// Slice off protocol fee
172179

173-
if (ctx.sParams.protocolFeeRecipient != address(0)) {
174-
uint256 protocolFeeAmount = feeAmount * ctx.sParams.protocolFee / 1e18;
180+
{
181+
(address protocolFeeRecipient, uint64 protocolFee) =
182+
EulerSwapProtocolFeeConfig(ctx.protocolFeeConfig).getProtocolFee(address(this));
175183

176-
if (protocolFeeAmount != 0) {
177-
IERC20(assetInput).safeTransfer(ctx.sParams.protocolFeeRecipient, protocolFeeAmount);
184+
if (protocolFee != 0) {
185+
uint256 protocolFeeAmount = feeAmount * protocolFee / 1e18;
178186

179-
amount -= protocolFeeAmount;
180-
feeAmount -= protocolFeeAmount;
187+
if (protocolFeeAmount != 0) {
188+
IERC20(assetInput).safeTransfer(protocolFeeRecipient, protocolFeeAmount);
189+
190+
amount -= protocolFeeAmount;
191+
feeAmount -= protocolFeeAmount;
192+
}
181193
}
182194
}
183195

0 commit comments

Comments
 (0)