1
1
/// SPDX-License-Identifier: SSPL-1.-0
2
2
3
- /**
4
- * @custom:org.protocol='mevETH LST Protocol'
5
- * @custom:org.security='mailto:security@manifoldfinance.com'
6
- * @custom:org.vcs-commit=$GIT_COMMIT_SHA
7
- * @custom:org.vendor='CommodityStream, Inc'
8
- * @custom:org.schema-version="1.0"
9
- * @custom.org.encryption="manifoldfinance.com/.well-known/pgp-key.asc"
10
- * @custom:org.preferred-languages="en"
11
- */
12
-
13
3
pragma solidity ^ 0.8.19 ;
14
4
15
- /*///////////// Manifold Mev Ether /////////////
5
+ /*///////////// Mev Protocol ////////// /////////////
16
6
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣷⣦⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠀⠀⠀⠀
17
7
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⣿⣷⣤⣀⠀⠀⠀⠀⠀⠉⠑⣶⣤⣄⣀⣠⣤⣶⣶⣿⣿⣿⣿⡇⠀⠀⠀
18
- ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⡿⠟⠋⠁⠀⠀⠀⣀⠤⠒⠉⠈⢉⡉⠻⢿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀
19
8
⠀⠀⠀⠀⣀⣴⣶⣿⣷⡄⠀⠀⠀⠀⢹⣿⣿⣿⣿⠏⠁⠀⢀⠄⠀⠀⠈⢀⠄⠀⢀⡖⠁⠀⢀⠀⠈⠻⣿⣿⣿⣿⡏⠀⠀⠀⠀
20
9
⠀⠀⢠⣾⣿⣿⣿⣿⡿⠁⠀⠀⠀⠀⢸⣿⣿⠏⠀⠀⢀⡴⠁⠀⠀⣠⠖⠁⢀⠞⠋⠀⢠⡇⢸⡄⠀⠀⠈⢻⣿⣿⠁⠀⠀⠀⠀
21
10
⠀⣠⣿⣿⣿⣿⣿⠟⠁⠀⠀⠀⠀⠀⢸⡿⠁⠀⠀⢀⡞⠀⠀⢀⡴⠃⠀⣰⠋⠀⠀⣰⡿⠀⡜⢳⡀⠘⣦⠀⢿⡇⠀⠀⠀⠀⠀
@@ -36,25 +25,24 @@ pragma solidity ^0.8.19;
36
25
⠀⠀⠀⠀⠀⠀⠀⠇⠀⠣⠀⡗⢣⡀⠘⢄⠀⢧⠀⢳⡟⠛⠙⣧⣧⣠⣄⣀⣠⢿⣶⠁⠀⠸⡀⠀⠓⠚⢴⣋⣠⠔⠀⠀⠀⠀⠁
37
26
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠧⡤⠙⢤⡈⣦⡼⠀⠀⠧⢶⠚⡇⠈⠁⠈⠃⠀⡰⢿⣄⠀⠀⠑⢤⣀⠀⠀⠀⠈⠁⠀⠀⠀⠀⠀
38
27
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
39
- /////////////////////////////////////////////*/
28
+ /////////////////////////////////////////////////// */
40
29
30
+ import { Auth } from "./libraries/Auth.sol " ;
41
31
import { SafeTransferLib } from "solmate/utils/SafeTransferLib.sol " ;
42
32
import { FixedPointMathLib } from "solmate/utils/FixedPointMathLib.sol " ;
33
+ import { ERC20 } from "solmate/tokens/ERC20.sol " ;
43
34
import { IERC4626 } from "./interfaces/IERC4626.sol " ;
44
35
import { WETH } from "solmate/tokens/WETH.sol " ;
45
36
import { MevEthErrors } from "./interfaces/Errors.sol " ;
46
37
import { IStakingModule } from "./interfaces/IStakingModule.sol " ;
47
- import { IMevEthShareVault } from "./interfaces/IMevEthShareVault.sol " ;
48
38
import { IERC20Burnable } from "./interfaces/IERC20Burnable.sol " ;
49
39
import { ITinyMevEth } from "./interfaces/ITinyMevEth.sol " ;
50
- import { WagyuStaker } from "./WagyuStaker.sol " ;
51
- import { OFTWithFee } from "./layerZero/oft/OFTWithFee.sol " ;
52
40
53
41
/// @title MevEth
54
- /// @author Manifold Finance
42
+ /// @author CommodityStream, Inc.
55
43
/// @dev Contract that allows deposit of ETH, for a Liquid Staking Receipt (LSR) in return.
56
44
/// @dev LSR is represented through an ERC4626 token and interface.
57
- contract MevEth is OFTWithFee , IERC4626 , ITinyMevEth {
45
+ contract MevEth is Auth , ERC20 , IERC4626 , ITinyMevEth {
58
46
using SafeTransferLib for WETH;
59
47
using FixedPointMathLib for uint256 ;
60
48
@@ -77,7 +65,9 @@ contract MevEth is OFTWithFee, IERC4626, ITinyMevEth {
77
65
/// @notice Max amount of ETH that can be deposited.
78
66
uint128 internal constant MAX_DEPOSIT = type (uint128 ).max;
79
67
/// @notice Min amount of ETH that can be deposited.
80
- uint128 public constant MIN_DEPOSIT = 0.01 ether ; // 0.01 eth
68
+ uint128 public constant MIN_DEPOSIT = 0.01 ether ;
69
+ /// @notice Min amount of ETH that can be withdrawn via the queue.
70
+ uint128 public MIN_WITHDRAWAL;
81
71
/// @notice The address of the MevEthShareVault.
82
72
address public mevEthShareVault;
83
73
/// @notice The address of the pending MevEthShareVault when a new vault has been committed but not finalized.
@@ -117,15 +107,9 @@ contract MevEth is OFTWithFee, IERC4626, ITinyMevEth {
117
107
/// @dev Pending staking module and committed timestamp will both be zero on deployment.
118
108
/// @param authority Address of the controlling admin authority.
119
109
/// @param weth Address of the WETH contract to use for deposits.
120
- /// @param layerZeroEndpoint Chain specific endpoint for LayerZero.
121
- constructor (
122
- address authority ,
123
- address weth ,
124
- address layerZeroEndpoint
125
- )
126
- OFTWithFee ("Mev Liquid Staked Ether " , "mevETH " , 18 , 8 , authority, layerZeroEndpoint)
127
- {
110
+ constructor (address authority , address weth ) Auth (authority) ERC20 ("Mev Liquid Staking Receipt " , "mevETH " , 18 ) {
128
111
WETH9 = WETH (payable (weth));
112
+ MIN_WITHDRAWAL = MIN_DEPOSIT;
129
113
}
130
114
131
115
/// @notice Calculate the needed Ether buffer required when creating a new validator.
@@ -194,19 +178,26 @@ contract MevEth is OFTWithFee, IERC4626, ITinyMevEth {
194
178
emit StakingUnpaused ();
195
179
}
196
180
197
- /// @notice Event emitted when a new staking module is committed. The MODULE_UPDATE_TIME_DELAY must elapse before the staking module update can be
198
- /// finalized.
181
+ /// @notice Event emitted when a new staking module is committed.
182
+ /// The MODULE_UPDATE_TIME_DELAY must elapse before the staking module update can be finalized.
199
183
event StakingModuleUpdateCommitted (address indexed oldModule , address indexed pendingModule , uint64 indexed eligibleForFinalization );
184
+
200
185
/// @notice Event emitted when a new staking module is finalized.
201
186
event StakingModuleUpdateFinalized (address indexed oldModule , address indexed newModule );
187
+
202
188
/// @notice Event emitted when a new pending module update is canceled.
203
189
event StakingModuleUpdateCanceled (address indexed oldModule , address indexed pendingModule );
204
190
205
- /// @notice Starts the process to update the staking module. To finalize the update, the MODULE_UPDATE_TIME_DELAY must elapse and the
206
- /// finalizeUpdateStakingModule function must be called.
191
+ /// @notice Starts the process to update the staking module.
192
+ /// To finalize the update, the MODULE_UPDATE_TIME_DELAY must elapse
193
+ /// and thefinalizeUpdateStakingModule function must be called.
207
194
/// @param newModule The new staking module.
208
195
/// @dev This function is only callable by addresses with the admin role.
209
196
function commitUpdateStakingModule (IStakingModule newModule ) external onlyAdmin {
197
+ if (address (newModule) == address (0 )) {
198
+ revert MevEthErrors.InvalidPendingStakingModule ();
199
+ }
200
+
210
201
pendingStakingModule = newModule;
211
202
pendingStakingModuleCommittedTimestamp = uint64 (block .timestamp );
212
203
emit StakingModuleUpdateCommitted (address (stakingModule), address (newModule), uint64 (block .timestamp + MODULE_UPDATE_TIME_DELAY));
@@ -352,6 +343,7 @@ contract MevEth is OFTWithFee, IERC4626, ITinyMevEth {
352
343
/// @notice Grants rewards updating the fraction.elastic.
353
344
/// @dev called from validator rewards updates
354
345
function grantRewards () external payable {
346
+ if (! (msg .sender == address (stakingModule) || msg .sender == mevEthShareVault)) revert MevEthErrors.UnAuthorizedCaller ();
355
347
if (msg .value == 0 ) revert MevEthErrors.ZeroValue ();
356
348
357
349
fraction.elastic += uint128 (msg .value );
@@ -444,6 +436,10 @@ contract MevEth is OFTWithFee, IERC4626, ITinyMevEth {
444
436
withdrawalAmountQueued += delta;
445
437
}
446
438
439
+ function setMinWithdrawal (uint128 newMinimum ) public onlyAdmin {
440
+ MIN_WITHDRAWAL = newMinimum;
441
+ }
442
+
447
443
/*//////////////////////////////////////////////////////////////
448
444
ERC4626 Support
449
445
//////////////////////////////////////////////////////////////*/
@@ -604,7 +600,7 @@ contract MevEth is OFTWithFee, IERC4626, ITinyMevEth {
604
600
/// @param shares shares that will be burned
605
601
function _withdraw (bool useQueue , address receiver , address owner , uint256 assets , uint256 shares ) internal {
606
602
// If withdraw is less than the minimum deposit / withdraw amount, revert
607
- if (assets < MIN_DEPOSIT ) revert MevEthErrors.WithdrawTooSmall ();
603
+ if (assets < MIN_WITHDRAWAL ) revert MevEthErrors.WithdrawTooSmall ();
608
604
// Sandwich protection
609
605
uint256 blockNumber = block .number ;
610
606
@@ -622,26 +618,28 @@ contract MevEth is OFTWithFee, IERC4626, ITinyMevEth {
622
618
_burn (owner, shares);
623
619
624
620
uint256 availableBalance = address (this ).balance - withdrawalAmountQueued; // available balance will be adjusted
625
-
621
+ uint256 amountToSend = assets;
626
622
if (availableBalance < assets) {
627
623
if (! useQueue) revert MevEthErrors.NotEnoughEth ();
628
- uint128 amountOwed = uint128 (assets - availableBalance);
624
+ // Available balance is sent, and the remainder must be withdrawn via the queue
625
+ uint256 amountOwed = assets - availableBalance;
629
626
++ queueLength;
630
627
withdrawalQueue[queueLength] = WithdrawalTicket ({
631
628
claimed: false ,
632
629
receiver: receiver,
633
- amount: amountOwed,
634
- accumulatedAmount: withdrawalQueue[queueLength - 1 ].accumulatedAmount + amountOwed
630
+ amount: uint128 ( amountOwed) ,
631
+ accumulatedAmount: withdrawalQueue[queueLength - 1 ].accumulatedAmount + uint128 ( amountOwed)
635
632
});
636
- emit WithdrawalQueueOpened (receiver, queueLength, uint256 (amountOwed));
637
- assets = availableBalance;
638
- shares = shares - convertToShares (amountOwed);
633
+ emit WithdrawalQueueOpened (receiver, queueLength, amountOwed);
634
+ amountToSend = availableBalance;
639
635
}
640
- if (assets != 0 ) {
636
+ if (amountToSend != 0 ) {
637
+ // As with ERC4626, we log assets and shares as if there is no queue, and everything has been withdrawn
638
+ // as this most closely resembles what is happened
641
639
emit Withdraw (msg .sender , owner, receiver, assets, shares);
642
640
643
- WETH9.deposit { value: assets }();
644
- WETH9.safeTransfer (receiver, assets );
641
+ WETH9.deposit { value: amountToSend }();
642
+ WETH9.safeTransfer (receiver, amountToSend );
645
643
}
646
644
}
647
645
0 commit comments