Contract Source Code:
/// SPDX-License-Identifier: SSPL-1.-0
pragma solidity ^0.8.19;
/*///////////// Mev Protocol ///////////////////////
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣷⣦⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⣿⣷⣤⣀⠀⠀⠀⠀⠀⠉⠑⣶⣤⣄⣀⣠⣤⣶⣶⣿⣿⣿⣿⡇⠀⠀⠀
⠀⠀⠀⠀⣀⣴⣶⣿⣷⡄⠀⠀⠀⠀⢹⣿⣿⣿⣿⠏⠁⠀⢀⠄⠀⠀⠈⢀⠄⠀⢀⡖⠁⠀⢀⠀⠈⠻⣿⣿⣿⣿⡏⠀⠀⠀⠀
⠀⠀⢠⣾⣿⣿⣿⣿⡿⠁⠀⠀⠀⠀⢸⣿⣿⠏⠀⠀⢀⡴⠁⠀⠀⣠⠖⠁⢀⠞⠋⠀⢠⡇⢸⡄⠀⠀⠈⢻⣿⣿⠁⠀⠀⠀⠀
⠀⣠⣿⣿⣿⣿⣿⠟⠁⠀⠀⠀⠀⠀⢸⡿⠁⠀⠀⢀⡞⠀⠀⢀⡴⠃⠀⣰⠋⠀⠀⣰⡿⠀⡜⢳⡀⠘⣦⠀⢿⡇⠀⠀⠀⠀⠀
⢠⣿⣿⣿⣿⡿⠃⠀⠀⠀⠀⠀⠀⢰⣿⠃⠀⢀⠆⡞⡄⠀⣠⡞⠁⣀⢾⠃⠀⣀⡜⢱⠇⣰⠁⠈⣷⠂⢸⡇⠸⣵⠀⠀⠀⠀⠀
⣿⣿⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⢠⣿⠇⠀⠀⡜⣸⡟⢀⣴⡏⢠⣾⠋⡎⢀⣼⠋⢀⡎⡰⠃⠀⠀⣿⣓⢒⡇⠀⣿⠀⠀⠀⠀⠀
⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠴⢻⣟⢀⣀⢀⣧⡇⢨⠟⢾⣔⡿⠃⢸⢀⠞⠃⢀⣾⡜⠁⠀⠀⠀⡏⠁⢠⠃⠀⢹⠀⠀⠀⠀⠀
⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⢸⣼⢸⣿⡟⢻⣿⠿⣶⣿⣿⣿⣶⣾⣏⣀⣠⣾⣿⠔⠒⠉⠉⢠⠁⡆⡸⠀⡈⣸⠀⠀⠀⠀⠀
⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⠀⠀⣸⣿⣸⣿⣇⢸⠃⡄⢻⠃⣾⣿⢋⠘⣿⣿⠏⣿⡟⣛⡛⢻⣿⢿⣶⣷⣿⣶⢃⣿⠀⠀⠀⠀⠀
⢸⣿⣿⣿⣿⣆⠀⠀⠀⠀⠀⣰⠃⣿⣿⣿⣿⠀⣸⣧⠈⣸⣿⠃⠘⠃⢹⣿⠀⣿⠃⠛⠛⣿⡇⢸⣿⡇⢸⣿⡿⣿⡀⠀⠀⠀⠀
⠀⠻⣿⣿⣿⣿⣦⡀⠀⢀⡔⣹⣼⡟⡟⣿⣿⣿⠛⠻⠶⠿⠷⣾⣿⣿⣬⣿⣠⣿⣀⣿⣿⣿⡇⠸⡿⠀⣾⡏⢠⣿⣇⠀⠀⠀⠀
⠀⠀⠙⢿⣿⣿⣿⣿⣷⡞⢠⣿⢿⡇⣿⡹⡝⢿⡷⣄⠀⠀⠀⠀⠀⠀⠀⠉⠉⠉⠙⠛⠛⠻⠿⣶⣶⣾⣿⣇⣾⠉⢯⠃⠀⠀⠀
⠀⠀⠀⠀⠙⠿⣿⣿⣿⠇⢸⠇⠘⣇⠸⡇⣿⣮⣳⡀⠉⠂⠀⠀⣀⣤⡤⢤⣀⠀⠀⠀⠀⠀⢈⣿⠟⣠⣾⠿⣿⡆⡄⣧⡀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠙⠻⡘⠾⣄⠀⠘⢦⣿⠃⠹⣿⣿⣶⠤⠀⠀⣿⠋⠉⠻⣿⠁⠀⠠⣀⣤⣾⣵⣾⡿⠃⣾⠏⣿⣧⠋⡇⠀⠀
⠀⠀⠀⠀⠀⠀⠀⣠⠖⠳⣄⡈⠃⠀⠼⠋⠙⢷⣞⢻⣿⣿⣀⡀⠈⠤⣀⠬⠟⠀⢀⣠⣶⠿⢛⡽⠋⣠⣾⣏⣠⡿⣃⣞⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⣧⠀⠀⠀⠉⠛⠓⠢⠶⣶⡤⠺⡟⢺⣿⠿⣿⣶⣤⣀⣠⣴⣾⡿⠿⢵⠋⠙⠲⣏⡝⠁⠀⣹⢿⡣⣌⠒⠄⠀
⠀⠀⠀⠀⠀⠀⢸⠈⡄⠀⠇⠀⠀⡖⠁⢢⡞⠀⢰⠻⣆⡏⣇⠙⠻⣿⣿⣿⣿⠋⢀⡴⣪⢷⡀⠀⡘⠀⢀⠜⠁⢀⠟⢆⠑⢄⠀
⠀⠀⠀⠀⠀⠀⠘⡄⠱⠀⠸⡀⠄⠳⡀⠀⢳⡀⢰⠀⢸⢇⡟⠑⠦⢈⡉⠁⢼⢠⡏⣴⠟⢙⠇⠀⡇⢠⠃⢀⡴⠁⠀⠘⠀⠈⡆
⠀⠀⠀⠀⠀⠀⠀⠇⠀⠣⠀⡗⢣⡀⠘⢄⠀⢧⠀⢳⡟⠛⠙⣧⣧⣠⣄⣀⣠⢿⣶⠁⠀⠸⡀⠀⠓⠚⢴⣋⣠⠔⠀⠀⠀⠀⠁
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠧⡤⠙⢤⡈⣦⡼⠀⠀⠧⢶⠚⡇⠈⠁⠈⠃⠀⡰⢿⣄⠀⠀⠑⢤⣀⠀⠀⠀⠈⠁⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
///////////////////////////////////////////////////*/
import { Auth } from "./libraries/Auth.sol";
import { SafeTransferLib } from "solmate/utils/SafeTransferLib.sol";
import { FixedPointMathLib } from "solmate/utils/FixedPointMathLib.sol";
import { ERC20 } from "solmate/tokens/ERC20.sol";
import { IERC4626 } from "./interfaces/IERC4626.sol";
import { WETH } from "solmate/tokens/WETH.sol";
import { MevEthErrors } from "./interfaces/Errors.sol";
import { IStakingModule } from "./interfaces/IStakingModule.sol";
import { IERC20Burnable } from "./interfaces/IERC20Burnable.sol";
import { ITinyMevEth } from "./interfaces/ITinyMevEth.sol";
/// @title MevEth
/// @author CommodityStream, Inc.
/// @dev Contract that allows deposit of ETH, for a Liquid Staking Receipt (LSR) in return.
/// @dev LSR is represented through an ERC4626 token and interface.
contract MevEth is Auth, ERC20, IERC4626, ITinyMevEth {
using SafeTransferLib for WETH;
using FixedPointMathLib for uint256;
/*//////////////////////////////////////////////////////////////
Configuration Variables
//////////////////////////////////////////////////////////////*/
/// @notice Inidicates if staking is paused.
bool public stakingPaused;
/// @notice Indicates if contract is initialized.
bool public initialized;
/// @notice withdraw fee denominator
uint16 internal constant feeDenominator = 10_000;
/// @notice Timestamp when pending staking module update can be finalized.
uint64 public pendingStakingModuleCommittedTimestamp;
/// @notice Timestamp when pending mevEthShareVault update can be finalized.
uint64 public pendingMevEthShareVaultCommittedTimestamp;
/// @notice Time delay before staking module or share vault can be finalized.
uint64 internal constant MODULE_UPDATE_TIME_DELAY = 7 days;
/// @notice Max amount of ETH that can be deposited.
uint128 internal constant MAX_DEPOSIT = type(uint128).max;
/// @notice Min amount of ETH that can be deposited.
uint128 public constant MIN_DEPOSIT = 0.01 ether;
/// @notice Min amount of ETH that can be withdrawn via the queue.
uint128 public MIN_WITHDRAWAL;
/// @notice The address of the MevEthShareVault.
address public mevEthShareVault;
/// @notice The address of the pending MevEthShareVault when a new vault has been committed but not finalized.
address public pendingMevEthShareVault;
/// @notice The staking module used to stake Ether.
IStakingModule public stakingModule;
/// @notice The pending staking module when a new module has been committed but not finalized.
IStakingModule public pendingStakingModule;
/// @notice WETH Implementation used by MevEth.
WETH public immutable WETH9;
/// @notice Last rewards payment by block number
uint256 internal lastRewards;
/// @notice Struct used to accounting the ETH staked within MevEth.
Fraction public fraction;
/// @notice The percent out of 1000 crETH2 can be redeemed for as mevEth
uint256 public constant CREAM_TO_MEV_ETH_PERCENT = 1130;
/// @notice The canonical address of the crETH2 address
address public constant creamToken = 0x49D72e3973900A195A155a46441F0C08179FdB64;
/// @notice Sandwich protection mapping of last user deposits by block number
mapping(address => uint256) lastDeposit;
/// @notice Deposited validators mapping to prevent double deposits
mapping(bytes => bool) depositedValidators;
/// @notice Central struct used for share accounting + math.
/// @custom:field elastic Represents total amount of staked ether, including rewards accrued / slashed.
/// @custom:field base Represents claims to ownership of the staked ether.
struct Fraction {
uint128 elastic;
uint128 base;
}
/*//////////////////////////////////////////////////////////////
Setup
//////////////////////////////////////////////////////////////*/
/// @notice Construction creates mevETH token, sets authority and weth address.
/// @dev Pending staking module and committed timestamp will both be zero on deployment.
/// @param authority Address of the controlling admin authority.
/// @param weth Address of the WETH contract to use for deposits.
constructor(address authority, address weth) Auth(authority) ERC20("Mev Liquid Staking Receipt", "mevETH", 18) {
WETH9 = WETH(payable(weth));
MIN_WITHDRAWAL = MIN_DEPOSIT;
}
/// @notice Calculate the needed Ether buffer required when creating a new validator.
/// @return uint256 The required Ether buffer.
function calculateNeededEtherBuffer() public view returns (uint256) {
unchecked {
return max(withdrawalAmountQueued, (stakingModule.VALIDATOR_DEPOSIT_SIZE() / 100) * 90);
}
}
/*//////////////////////////////////////////////////////////////
Admin Control Panel
//////////////////////////////////////////////////////////////*/
/// @notice Event emitted when the MevEth is successfully initialized.
event MevEthInitialized(address indexed mevEthShareVault, address indexed stakingModule);
/// @notice Initializes the MevEth contract, setting the staking module and share vault addresses.
/// @param initialShareVault The initial share vault set during initialization.
/// @param initialStakingModule The initial staking module set during initialization.
/// @dev This function can only be called once and is protected by the onlyAdmin modifier.
function init(address initialShareVault, address initialStakingModule) external onlyAdmin {
// Revert if the initial share vault or staking module is the zero address.
if (initialShareVault == address(0)) {
revert MevEthErrors.ZeroAddress();
}
if (initialStakingModule == address(0)) {
revert MevEthErrors.ZeroAddress();
}
// Revert if the contract has already been initialized.
if (initialized) {
revert MevEthErrors.AlreadyInitialized();
}
// Update state variables and emit event to notify offchain listeners that the contract has been initialized.
initialized = true;
mevEthShareVault = initialShareVault;
stakingModule = IStakingModule(initialStakingModule);
emit MevEthInitialized(initialShareVault, initialStakingModule);
}
/// @notice Emitted when staking is paused.
event StakingPaused();
/// @notice Emitted when staking is unpaused.
event StakingUnpaused();
/// @notice Ensures that staking is not paused when invoking a specific function.
/// @dev This check is used on the createValidator, deposit and mint functions.
function _stakingUnpaused() internal view {
if (stakingPaused) revert MevEthErrors.StakingPaused();
}
/// @notice Pauses staking on the MevEth contract.
/// @dev This function is only callable by addresses with the admin role.
function pauseStaking() external onlyAdmin {
stakingPaused = true;
emit StakingPaused();
}
/// @notice Unauses staking on the MevEth contract.
/// @dev This function is only callable by addresses with the admin role.
function unpauseStaking() external onlyAdmin {
stakingPaused = false;
emit StakingUnpaused();
}
/// @notice Event emitted when a new staking module is committed.
/// The MODULE_UPDATE_TIME_DELAY must elapse before the staking module update can be finalized.
event StakingModuleUpdateCommitted(address indexed oldModule, address indexed pendingModule, uint64 indexed eligibleForFinalization);
/// @notice Event emitted when a new staking module is finalized.
event StakingModuleUpdateFinalized(address indexed oldModule, address indexed newModule);
/// @notice Event emitted when a new pending module update is canceled.
event StakingModuleUpdateCanceled(address indexed oldModule, address indexed pendingModule);
/// @notice Starts the process to update the staking module.
/// To finalize the update, the MODULE_UPDATE_TIME_DELAY must elapse
/// and thefinalizeUpdateStakingModule function must be called.
/// @param newModule The new staking module.
/// @dev This function is only callable by addresses with the admin role.
function commitUpdateStakingModule(IStakingModule newModule) external onlyAdmin {
if (address(newModule) == address(0)) {
revert MevEthErrors.InvalidPendingStakingModule();
}
pendingStakingModule = newModule;
pendingStakingModuleCommittedTimestamp = uint64(block.timestamp);
emit StakingModuleUpdateCommitted(address(stakingModule), address(newModule), uint64(block.timestamp + MODULE_UPDATE_TIME_DELAY));
}
/// @notice Finalizes the staking module update if a pending staking module exists.
/// @dev This function is only callable by addresses with the admin role.
function finalizeUpdateStakingModule() external onlyAdmin {
// Revert if there is no pending staking module or if the the staking module finalization is premature.
uint64 committedTimestamp = pendingStakingModuleCommittedTimestamp;
if (address(pendingStakingModule) == address(0) || committedTimestamp == 0) {
revert MevEthErrors.InvalidPendingStakingModule();
}
if (uint64(block.timestamp) < committedTimestamp + MODULE_UPDATE_TIME_DELAY) {
revert MevEthErrors.PrematureStakingModuleUpdateFinalization();
}
// Emit an event to notify offchain listeners that the staking module has been finalized.
emit StakingModuleUpdateFinalized(address(stakingModule), address(pendingStakingModule));
// Update the staking module
stakingModule = pendingStakingModule;
// Set the pending staking module variables to zero.
pendingStakingModule = IStakingModule(address(0));
pendingStakingModuleCommittedTimestamp = 0;
}
/// @notice Cancels a pending staking module update.
/// @dev This function is only callable by addresses with the admin role.
function cancelUpdateStakingModule() external onlyAdmin {
// Revert if there is no pending staking module.
if (address(pendingStakingModule) == address(0) || pendingStakingModuleCommittedTimestamp == 0) {
revert MevEthErrors.InvalidPendingStakingModule();
}
// Emit an event to notify offchain listeners that the staking module has been canceled.
emit StakingModuleUpdateCanceled(address(stakingModule), address(pendingStakingModule));
// Set the pending staking module variables to zero.
pendingStakingModule = IStakingModule(address(0));
pendingStakingModuleCommittedTimestamp = 0;
}
/// @notice Event emitted when a new share vault is committed. To finalize the update, the MODULE_UPDATE_TIME_DELAY must elapse and the
/// finalizeUpdateMevEthShareVault function must be called.
event MevEthShareVaultUpdateCommitted(address indexed oldVault, address indexed pendingVault, uint64 indexed eligibleForFinalization);
/// @notice Event emitted when a new share vault is finalized.
event MevEthShareVaultUpdateFinalized(address indexed oldVault, address indexed newVault);
/// @notice Event emitted when a new pending share vault update is canceled.
event MevEthShareVaultUpdateCanceled(address indexed oldVault, address indexed newVault);
/// @notice Starts the process to update the share vault. To finalize the update, the MODULE_UPDATE_TIME_DELAY must elapse and the
/// finalizeUpdateStakingModule function must be called.
/// @param newMevEthShareVault The new share vault
/// @dev This function is only callable by addresses with the admin role
function commitUpdateMevEthShareVault(address newMevEthShareVault) external onlyAdmin {
if (newMevEthShareVault == address(0)) {
revert MevEthErrors.ZeroAddress();
}
pendingMevEthShareVault = newMevEthShareVault;
pendingMevEthShareVaultCommittedTimestamp = uint64(block.timestamp);
emit MevEthShareVaultUpdateCommitted(mevEthShareVault, newMevEthShareVault, uint64(block.timestamp + MODULE_UPDATE_TIME_DELAY));
}
/// @notice Finalizes the share vault update if a pending share vault exists.
/// @dev This function is only callable by addresses with the admin role.
function finalizeUpdateMevEthShareVault() external onlyAdmin {
// Revert if there is no pending share vault or if the the share vault finalization is premature.
uint64 committedTimestamp = pendingMevEthShareVaultCommittedTimestamp;
if (pendingMevEthShareVault == address(0) || committedTimestamp == 0) {
revert MevEthErrors.InvalidPendingMevEthShareVault();
}
if (uint64(block.timestamp) < committedTimestamp + MODULE_UPDATE_TIME_DELAY) {
revert MevEthErrors.PrematureMevEthShareVaultUpdateFinalization();
}
/// @custom:: When finalizing the update to the MevEthShareVault, make sure to grant any remaining rewards from the existing share vault.
// Emit an event to notify offchain listeners that the share vault has been finalized.
emit MevEthShareVaultUpdateFinalized(mevEthShareVault, address(pendingMevEthShareVault));
// Update the mev share vault
mevEthShareVault = pendingMevEthShareVault;
// Set the pending vault variables to zero
pendingMevEthShareVault = address(0);
pendingMevEthShareVaultCommittedTimestamp = 0;
}
/// @notice Cancels a pending share vault update.
/// @dev This function is only callable by addresses with the admin role.
function cancelUpdateMevEthShareVault() external onlyAdmin {
// Revert if there is no pending share vault.
if (pendingMevEthShareVault == address(0) || pendingMevEthShareVaultCommittedTimestamp == 0) {
revert MevEthErrors.InvalidPendingMevEthShareVault();
}
// Emit an event to notify offchain listeners that the share vault has been canceled.
emit MevEthShareVaultUpdateCanceled(mevEthShareVault, pendingMevEthShareVault);
//Set the pending vault variables to zero
pendingMevEthShareVault = address(0);
pendingMevEthShareVaultCommittedTimestamp = 0;
}
/*//////////////////////////////////////////////////////////////
Registry For Validators
//////////////////////////////////////////////////////////////*/
/// @notice Event emitted when a new validator is created
event ValidatorCreated(address indexed stakingModule, IStakingModule.ValidatorData newValidator);
/// @notice This function passes through the needed Ether to the Staking module, and the assosiated credentials with it
/// @param newData The data needed to create a new validator
/// @dev This function is only callable by addresses with the operator role and if staking is unpaused
function createValidator(IStakingModule.ValidatorData calldata newData, bytes32 latestDepositRoot) external onlyOperator {
// check if staking is paused
_stakingUnpaused();
// check validator does not already exist
if (depositedValidators[newData.pubkey]) revert MevEthErrors.AlreadyDeposited();
// set validator deposited to true
depositedValidators[newData.pubkey] = true;
IStakingModule _stakingModule = stakingModule;
// check withdrawal address is correct
if (address(_stakingModule) != address(uint160(uint256(newData.withdrawal_credentials)))) revert MevEthErrors.IncorrectWithdrawalCredentials();
// Determine how big deposit is for the validator
uint256 depositSize = _stakingModule.VALIDATOR_DEPOSIT_SIZE();
if (address(this).balance < depositSize + calculateNeededEtherBuffer()) {
revert MevEthErrors.NotEnoughEth();
}
// Deposit the Ether into the staking contract
_stakingModule.deposit{ value: depositSize }(newData, latestDepositRoot);
emit ValidatorCreated(address(_stakingModule), newData);
}
/// @notice Event emitted when rewards are granted.
event Rewards(address sender, uint256 amount);
/// @notice Grants rewards updating the fraction.elastic.
/// @dev called from validator rewards updates
function grantRewards() external payable {
if (!(msg.sender == address(stakingModule) || msg.sender == mevEthShareVault)) revert MevEthErrors.UnAuthorizedCaller();
if (msg.value == 0) revert MevEthErrors.ZeroValue();
fraction.elastic += uint128(msg.value);
lastRewards = block.number;
emit Rewards(msg.sender, msg.value);
}
/// @notice Emitted when validator withdraw funds are received.
event ValidatorWithdraw(address sender, uint256 amount);
/// @notice Allows the MevEthShareVault or the staking module to withdraw validator funds from the contract.
/// @dev Before updating the fraction, the withdrawal queue is processed, which pays out any pending withdrawals.
/// @dev This function is only callable by the MevEthShareVault or the staking module.
function grantValidatorWithdraw() external payable {
// Check that the sender is the staking module or the MevEthShareVault.
if (!(msg.sender == address(stakingModule) || msg.sender == mevEthShareVault)) revert MevEthErrors.InvalidSender();
// Check that the value is not zero
if (msg.value != 32 ether) {
revert MevEthErrors.WrongWithdrawAmount();
}
// Emit an event to notify offchain listeners that a validator has withdrawn funds.
emit ValidatorWithdraw(msg.sender, msg.value);
// Register our exit with the staking module
stakingModule.registerExit();
}
/*//////////////////////////////////////////////////////////////
WITHDRAWAL QUEUE
//////////////////////////////////////////////////////////////*/
/// @notice Struct representing a withdrawal ticket which is added to the withdrawal queue.
/// @custom:field claimed True if this receiver has received ticket funds.
/// @custom:field receiver The receiever of the ETH specified in the WithdrawalTicket.
/// @custom:field amount The amount of ETH to send to the receiver when the ticket is processed.
/// @custom:field accumulatedAmount Keep a running sum of all requested ETH
struct WithdrawalTicket {
bool claimed;
address receiver;
uint128 amount;
uint128 accumulatedAmount;
}
/// @notice Event emitted when a withdrawal ticket is added to the queue.
event WithdrawalQueueOpened(address indexed recipient, uint256 indexed withdrawalId, uint256 assets);
event WithdrawalQueueClosed(address indexed recipient, uint256 indexed withdrawalId, uint256 assets);
/// @notice The length of the withdrawal queue.
uint256 public queueLength;
/// @notice mark the latest withdrawal request that was finalised
uint256 public requestsFinalisedUntil;
/// @notice Withdrawal amount queued
uint256 public withdrawalAmountQueued;
/// @notice The mapping representing the withdrawal queue.
/// @dev The index in the queue is the key, and the value is the WithdrawalTicket.
mapping(uint256 ticketNumber => WithdrawalTicket ticket) public withdrawalQueue;
/// @notice Claim Finalised Withdrawal Ticket
/// @param withdrawalId Unique ID of the withdrawal ticket
function claim(uint256 withdrawalId) external {
if (withdrawalId > requestsFinalisedUntil) revert MevEthErrors.NotFinalised();
WithdrawalTicket storage ticket = withdrawalQueue[withdrawalId];
if (ticket.claimed) revert MevEthErrors.AlreadyClaimed();
withdrawalQueue[withdrawalId].claimed = true;
withdrawalAmountQueued -= uint256(ticket.amount);
emit WithdrawalQueueClosed(ticket.receiver, withdrawalId, uint256(ticket.amount));
WETH9.deposit{ value: uint256(ticket.amount) }();
WETH9.safeTransfer(ticket.receiver, uint256(ticket.amount));
}
/// @notice Processes the withdrawal queue, reserving any pending withdrawals with the contract's available balance.
function processWithdrawalQueue(uint256 newRequestsFinalisedUntil) external onlyOperator {
if (newRequestsFinalisedUntil > queueLength) revert MevEthErrors.IndexExceedsQueueLength();
uint256 balance = address(this).balance;
if (withdrawalAmountQueued >= balance) revert MevEthErrors.NotEnoughEth();
uint256 available = balance - withdrawalAmountQueued;
uint256 finalised = requestsFinalisedUntil;
if (newRequestsFinalisedUntil < finalised) revert MevEthErrors.AlreadyFinalised();
uint256 delta = uint256(withdrawalQueue[newRequestsFinalisedUntil].accumulatedAmount - withdrawalQueue[finalised].accumulatedAmount);
if (available < delta) revert MevEthErrors.NotEnoughEth();
requestsFinalisedUntil = newRequestsFinalisedUntil;
withdrawalAmountQueued += delta;
}
function setMinWithdrawal(uint128 newMinimum) public onlyAdmin {
MIN_WITHDRAWAL = newMinimum;
}
/*//////////////////////////////////////////////////////////////
ERC4626 Support
//////////////////////////////////////////////////////////////*/
/// @notice The underlying asset of the mevEth contract
/// @return assetTokenAddress The address of the asset token
function asset() external view returns (address assetTokenAddress) {
assetTokenAddress = address(WETH9);
}
/// @notice The total amount of assets controlled by the mevEth contract
/// @return totalManagedAssets The amount of eth controlled by the mevEth contract
function totalAssets() external view returns (uint256 totalManagedAssets) {
// Should return the total amount of Ether managed by the contract
totalManagedAssets = uint256(fraction.elastic);
}
/// @notice Function to convert a specified amount of assets to shares based on the elastic and base.
/// @param assets The amount of assets to convert to shares
/// @return shares The value of the given assets in shares
function convertToShares(uint256 assets) public view returns (uint256 shares) {
// So if there are no shares, then they will mint 1:1 with assets
// Otherwise, shares will mint proportional to the amount of assets
if ((uint256(fraction.elastic) == 0) || (uint256(fraction.base) == 0)) {
shares = assets;
} else {
shares = (assets * uint256(fraction.base)) / uint256(fraction.elastic);
}
}
/// @notice Function to convert a specified amount of shares to assets based on the elastic and base.
/// @param shares The amount of shares to convert to assets
/// @return assets The value of the given shares in assets
function convertToAssets(uint256 shares) public view returns (uint256 assets) {
// So if there are no shares, then they will mint 1:1 with assets
// Otherwise, shares will mint proportional to the amount of assets
if (uint256(fraction.elastic) == 0 || uint256(fraction.base) == 0) {
assets = shares;
} else {
assets = (shares * uint256(fraction.elastic)) / uint256(fraction.base);
}
}
/// @notice Function to indicate the maximum deposit possible.
/// @return maxAssets The maximum amount of assets that can be deposited.
function maxDeposit(address) external view returns (uint256 maxAssets) {
// If staking is paused, then no deposits can be made
if (stakingPaused) {
return 0;
}
// No practical limit on deposit for Ether
maxAssets = uint256(MAX_DEPOSIT);
}
/// @notice Function to simulate the amount of shares that would be minted for a given deposit at the current ratio.
/// @param assets The amount of assets that would be deposited
/// @return shares The amount of shares that would be minted, *under ideal conditions* only
function previewDeposit(uint256 assets) external view returns (uint256 shares) {
return convertToShares(assets);
}
/// @notice internal deposit function to process Weth or Eth deposits
/// @param receiver The address user whom should receive the mevEth out
/// @param assets The amount of assets to deposit
/// @param shares The amount of shares that should be minted
function _deposit(address receiver, uint256 assets, uint256 shares) internal {
// If the deposit is less than the minimum deposit, revert
if (assets < MIN_DEPOSIT) revert MevEthErrors.DepositTooSmall();
fraction.elastic += uint128(assets);
fraction.base += uint128(shares);
// Update last deposit block for the user recorded for sandwich protection
lastDeposit[msg.sender] = block.number;
lastDeposit[receiver] = block.number;
if (msg.value == 0) {
WETH9.safeTransferFrom(msg.sender, address(this), assets);
WETH9.withdraw(assets);
} else {
if (msg.value != assets) revert MevEthErrors.WrongDepositAmount();
}
// Mint MevEth shares to the receiver
_mint(receiver, shares);
// Emit the deposit event to notify offchain listeners that a deposit has occured
emit Deposit(msg.sender, receiver, assets, shares);
}
/// @notice Function to deposit assets into the mevEth contract
/// @param assets The amount of WETH which should be deposited
/// @param receiver The address user whom should receive the mevEth out
/// @return shares The amount of shares minted
function deposit(uint256 assets, address receiver) external payable returns (uint256 shares) {
_stakingUnpaused();
// Convert the assets to shares and update the fraction elastic and base
shares = convertToShares(assets);
// Deposit the assets
_deposit(receiver, assets, shares);
}
/// @notice Function to indicate the maximum amount of shares that can be minted at the current ratio.
/// @return maxShares The maximum amount of shares that can be minted
function maxMint(address) external view returns (uint256 maxShares) {
// If staking is paused, no shares can be minted
if (stakingPaused) {
return 0;
}
// No practical limit on mint for Ether
return MAX_DEPOSIT;
}
/// @notice Function to simulate the amount of assets that would be required to mint a given amount of shares at the current ratio.
/// @param shares The amount of shares that would be minted
/// @return assets The amount of assets that would be required, *under ideal conditions* only
function previewMint(uint256 shares) external view returns (uint256 assets) {
return convertToAssets(shares);
}
/// @notice Function to mint shares of the mevEth contract
/// @param shares The amount of shares that should be minted
/// @param receiver The address user whom should receive the mevEth out
/// @return assets The amount of assets deposited
function mint(uint256 shares, address receiver) external payable returns (uint256 assets) {
_stakingUnpaused();
// Convert the shares to assets and update the fraction elastic and base
assets = convertToAssets(shares);
// Deposit the assets
_deposit(receiver, assets, shares);
}
/// @notice Function to indicate the maximum amount of assets that can be withdrawn at the current state.
/// @param owner The address in question of who would be withdrawing
/// @return maxAssets The maximum amount of assets that can be withdrawn
function maxWithdraw(address owner) external view returns (uint256 maxAssets) {
// Withdrawal is either their maximum balance, or the internal buffer
maxAssets = min(address(this).balance, convertToAssets(balanceOf[owner]));
}
/// @notice Function to simulate the amount of shares that would be allocated for a specified amount of assets.
/// @param assets The amount of assets that would be withdrawn
/// @return shares The amount of shares that would be burned, *under ideal conditions* only
function previewWithdraw(uint256 assets) external view returns (uint256 shares) {
// withdraw fee fixed at 0.01%
uint256 fee = assets / uint256(feeDenominator);
shares = convertToShares(assets + fee);
}
///@notice Function to withdraw assets from the mevEth contract
/// @param useQueue Flag whether to use the withdrawal queue
/// @param receiver The address user whom should receive the mevEth out
/// @param owner The address of the owner of the mevEth
/// @param assets The amount of assets that should be withdrawn
/// @param shares shares that will be burned
function _withdraw(bool useQueue, address receiver, address owner, uint256 assets, uint256 shares) internal {
// If withdraw is less than the minimum deposit / withdraw amount, revert
if (assets < MIN_WITHDRAWAL) revert MevEthErrors.WithdrawTooSmall();
// Sandwich protection
uint256 blockNumber = block.number;
if (((blockNumber - lastDeposit[msg.sender]) == 0 || (blockNumber - lastDeposit[owner] == 0)) && (blockNumber - lastRewards) == 0) {
revert MevEthErrors.SandwichProtection();
}
_updateAllowance(owner, shares);
// Update the elastic and base
fraction.elastic -= uint128(assets);
fraction.base -= uint128(shares);
// Burn the shares and emit a withdraw event for offchain listeners to know that a withdraw has occured
_burn(owner, shares);
uint256 availableBalance = address(this).balance - withdrawalAmountQueued; // available balance will be adjusted
uint256 amountToSend = assets;
if (availableBalance < assets) {
if (!useQueue) revert MevEthErrors.NotEnoughEth();
// Available balance is sent, and the remainder must be withdrawn via the queue
uint256 amountOwed = assets - availableBalance;
++queueLength;
withdrawalQueue[queueLength] = WithdrawalTicket({
claimed: false,
receiver: receiver,
amount: uint128(amountOwed),
accumulatedAmount: withdrawalQueue[queueLength - 1].accumulatedAmount + uint128(amountOwed)
});
emit WithdrawalQueueOpened(receiver, queueLength, amountOwed);
amountToSend = availableBalance;
}
if (amountToSend != 0) {
// As with ERC4626, we log assets and shares as if there is no queue, and everything has been withdrawn
// as this most closely resembles what is happened
emit Withdraw(msg.sender, owner, receiver, assets, shares);
WETH9.deposit{ value: amountToSend }();
WETH9.safeTransfer(receiver, amountToSend);
}
}
/// @dev internal function to update allowance for withdraws if necessary
/// @param owner owner of tokens
/// @param shares amount of shares to update
function _updateAllowance(address owner, uint256 shares) internal {
uint256 allowed = allowance[owner][msg.sender];
if (owner != msg.sender) {
if (allowed < shares) revert MevEthErrors.TransferExceedsAllowance();
if (allowed != type(uint256).max) {
unchecked {
allowance[owner][msg.sender] -= shares;
}
}
}
}
/// @notice Withdraw assets if balance is available
/// @param assets The amount of assets that should be withdrawn
/// @param receiver The address user whom should receive the mevEth out
/// @param owner The address of the owner of the mevEth
/// @return shares The amount of shares burned
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares) {
// withdraw fee fixed at 0.01%
uint256 fee = assets / uint256(feeDenominator);
// Convert the assets to shares and check if the owner has the allowance to withdraw the shares.
shares = convertToShares(assets + fee);
// Withdraw the assets from the MevEth contract
_withdraw(false, receiver, owner, assets, shares);
}
/// @notice Withdraw assets or open queue ticket for claim depending on balance available
/// @param assets The amount of assets that should be withdrawn
/// @param receiver The address user whom should receive the mevEth out
/// @param owner The address of the owner of the mevEth
/// @return shares The amount of shares burned
function withdrawQueue(uint256 assets, address receiver, address owner) external returns (uint256 shares) {
// withdraw fee fixed at 0.01%
uint256 fee = assets / uint256(feeDenominator);
// last shareholder has no fee
if ((fraction.elastic - assets) == 0) fee = 0;
// Convert the assets to shares and check if the owner has the allowance to withdraw the shares.
shares = convertToShares(assets + fee);
// Withdraw the assets from the MevEth contract
_withdraw(true, receiver, owner, assets, shares);
}
///@notice Function to simulate the maximum amount of shares that can be redeemed by the owner.
/// @param owner The address in question of who would be redeeming their shares
/// @return maxShares The maximum amount of shares they could redeem
function maxRedeem(address owner) external view returns (uint256 maxShares) {
maxShares = min(convertToShares(address(this).balance), balanceOf[owner]);
}
/// @notice Function to simulate the amount of assets that would be withdrawn for a specified amount of shares.
/// @param shares The amount of shares that would be burned
/// @return assets The amount of assets that would be withdrawn, *under ideal conditions* only
function previewRedeem(uint256 shares) external view returns (uint256 assets) {
// withdraw fee fixed at 0.01%
uint256 fee = shares / uint256(feeDenominator);
assets = convertToAssets(shares - fee);
}
/// @notice Function to redeem shares from the mevEth contract
/// @param shares The amount of shares that should be burned
/// @param receiver The address user whom should receive the wETH out
/// @param owner The address of the owner of the mevEth
/// @return assets The amount of assets withdrawn
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets) {
// withdraw fee fixed at 0.01%
uint256 fee = shares / uint256(feeDenominator);
// last shareholder has no fee
if ((totalSupply - shares) == 0) fee = 0;
// Convert the shares to assets and check if the owner has the allowance to withdraw the shares.
assets = convertToAssets(shares - fee);
// Withdraw the assets from the MevEth contract
_withdraw(false, receiver, owner, assets, shares);
}
/*//////////////////////////////////////////////////////////////
Utility Functions
//////////////////////////////////////////////////////////////*/
/// @dev Returns the largest of two numbers.
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/// @dev Returns the smallest of two numbers.
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/*//////////////////////////////////////////////////////////////
Special CreamEth2 redeem (from initial migration)
//////////////////////////////////////////////////////////////*/
/// @notice Redeem Cream staked eth tokens for mevETH at a fixed ratio
/// @param creamAmount The amount of Cream tokens to redeem
function redeemCream(uint256 creamAmount) external {
_stakingUnpaused();
if (creamAmount == 0) revert MevEthErrors.ZeroValue();
// Calculate the equivalent mevETH to be redeemed based on the ratio
uint256 assets = creamAmount * uint256(CREAM_TO_MEV_ETH_PERCENT) / 1000;
if (assets < MIN_DEPOSIT) revert MevEthErrors.DepositTooSmall();
// Convert the shares to assets and update the fraction elastic and base
uint256 shares = convertToShares(assets);
fraction.elastic += uint128(assets);
fraction.base += uint128(shares);
// Burn CreamEth2 tokens
IERC20Burnable(creamToken).burnFrom(msg.sender, creamAmount);
// Mint the equivalent mevETH
_mint(msg.sender, shares);
// Emit event
emit CreamRedeemed(msg.sender, creamAmount, shares);
}
// Event emitted when Cream tokens are redeemed for mevETH
event CreamRedeemed(address indexed redeemer, uint256 creamAmount, uint256 mevEthAmount);
/// @dev Only Weth withdraw is defined for the behaviour. Deposits should be directed to deposit / mint. Rewards via grantRewards and validator withdraws
/// via grantValidatorWithdraw.
receive() external payable {
if (msg.sender != address(WETH9)) revert MevEthErrors.InvalidSender();
}
function transfer(address to, uint256 amount) public virtual override returns (bool) {
uint256 lastDepositFrom = lastDeposit[msg.sender];
if (lastDepositFrom > lastDeposit[to]) {
lastDeposit[to] = lastDepositFrom;
}
return super.transfer(to, amount);
}
function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
uint256 lastDepositFrom = lastDeposit[from];
if (lastDepositFrom > lastDeposit[to]) {
lastDeposit[to] = lastDepositFrom;
}
return super.transferFrom(from, to, amount);
}
}
/// SPDX-License-Identifier: SSPL-1.-0
/**
* @custom:org.protocol='mevETH LST Protocol'
* @custom:org.security='mailto:[email protected]'
* @custom:org.vcs-commit=$GIT_COMMIT_SHA
* @custom:org.vendor='CommodityStream, Inc'
* @custom:org.schema-version="1.0"
* @custom.org.encryption="manifoldfinance.com/.well-known/pgp-key.asc"
* @custom:org.preferred-languages="en"
*/
pragma solidity ^0.8.19;
contract Auth {
error Unauthorized();
error AlreadySet();
error NoAdmin();
event AdminAdded(address indexed newAdmin);
event AdminDeleted(address indexed oldAdmin);
event OperatorAdded(address indexed newOperator);
event OperatorDeleted(address indexed oldOperator);
// admin counter (assuming 255 admins to be max)
uint8 adminsCounter;
// Keeps track of all operators
mapping(address => bool) public operators;
// Keeps track of all admins
mapping(address => bool) public admins;
/**
* @notice This constructor sets the initialAdmin address as an admin and operator.
* @dev The adminsCounter is incremented unchecked.
*/
constructor(address initialAdmin) {
admins[initialAdmin] = true;
unchecked {
++adminsCounter;
}
operators[initialAdmin] = true;
}
/*//////////////////////////////////////////////////////////////
Access Control Modifiers
//////////////////////////////////////////////////////////////*/
modifier onlyAdmin() {
if (!admins[msg.sender]) {
revert Unauthorized();
}
_;
}
modifier onlyOperator() {
if (!operators[msg.sender]) {
revert Unauthorized();
}
_;
}
/*//////////////////////////////////////////////////////////////
Maintenance Functions
//////////////////////////////////////////////////////////////*/
/**
* @notice addAdmin() function allows an admin to add a new admin to the contract.
* @dev This function is only accessible to the existing admins and requires the address of the new admin.
* If the new admin is already set, the function will revert. Otherwise, the adminsCounter will be incremented and the new admin will be added to the admins
* mapping. An AdminAdded event will be emitted.
*/
function addAdmin(address newAdmin) external onlyAdmin {
if (admins[newAdmin]) revert AlreadySet();
++adminsCounter;
admins[newAdmin] = true;
emit AdminAdded(newAdmin);
}
/**
* @notice Deletes an admin from the list of admins.
* @dev Only admins can delete other admins. If the adminsCounter is 0, the transaction will revert.
*/
function deleteAdmin(address oldAdmin) external onlyAdmin {
if (!admins[oldAdmin]) revert AlreadySet();
--adminsCounter;
if (adminsCounter == 0) revert NoAdmin();
admins[oldAdmin] = false;
emit AdminDeleted(oldAdmin);
}
/**
* @notice Adds a new operator to the list of operators
* @dev Only the admin can add a new operator
* @param newOperator The address of the new operator
*/
function addOperator(address newOperator) external onlyAdmin {
if (operators[newOperator]) revert AlreadySet();
operators[newOperator] = true;
emit OperatorAdded(newOperator);
}
function deleteOperator(address oldOperator) external onlyAdmin {
if (!operators[oldOperator]) revert AlreadySet();
operators[oldOperator] = false;
emit OperatorDeleted(oldOperator);
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {ERC20} from "../tokens/ERC20.sol";
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Caution! This library won't check that a token has code, responsibility is delegated to the caller.
library SafeTransferLib {
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferETH(address to, uint256 amount) internal {
bool success;
assembly {
// Transfer the ETH and store if it succeeded or not.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferFrom(
ERC20 token,
address from,
address to,
uint256 amount
) internal {
bool success;
assembly {
// We'll write our calldata to this slot below, but restore it later.
let memPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(0, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(4, from) // Append the "from" argument.
mstore(36, to) // Append the "to" argument.
mstore(68, amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because that's the total length of our calldata (4 + 32 * 3)
// Counterintuitively, this call() must be positioned after the or() in the
// surrounding and() because and() evaluates its arguments from right to left.
call(gas(), token, 0, 0, 100, 0, 32)
)
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, memPointer) // Restore the memPointer.
}
require(success, "TRANSFER_FROM_FAILED");
}
function safeTransfer(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
assembly {
// We'll write our calldata to this slot below, but restore it later.
let memPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(0, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(4, to) // Append the "to" argument.
mstore(36, amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because that's the total length of our calldata (4 + 32 * 2)
// Counterintuitively, this call() must be positioned after the or() in the
// surrounding and() because and() evaluates its arguments from right to left.
call(gas(), token, 0, 0, 68, 0, 32)
)
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, memPointer) // Restore the memPointer.
}
require(success, "TRANSFER_FAILED");
}
function safeApprove(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
assembly {
// We'll write our calldata to this slot below, but restore it later.
let memPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(0, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(4, to) // Append the "to" argument.
mstore(36, amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because that's the total length of our calldata (4 + 32 * 2)
// Counterintuitively, this call() must be positioned after the or() in the
// surrounding and() because and() evaluates its arguments from right to left.
call(gas(), token, 0, 0, 68, 0, 32)
)
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, memPointer) // Restore the memPointer.
}
require(success, "APPROVE_FAILED");
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/FixedPointMathLib.sol)
library FixedPointMathLib {
/*//////////////////////////////////////////////////////////////
SIMPLIFIED FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.
function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
}
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
}
function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
}
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
}
function powWad(int256 x, int256 y) internal pure returns (int256) {
// Equivalent to x to the power of y because x ** y = (e ** ln(x)) ** y = e ** (ln(x) * y)
return expWad((lnWad(x) * y) / int256(WAD)); // Using ln(x) means x must be greater than 0.
}
function expWad(int256 x) internal pure returns (int256 r) {
unchecked {
// When the result is < 0.5 we return zero. This happens when
// x <= floor(log(0.5e18) * 1e18) ~ -42e18
if (x <= -42139678854452767551) return 0;
// When the result is > (2**255 - 1) / 1e18 we can not represent it as an
// int. This happens when x >= floor(log((2**255 - 1) / 1e18) * 1e18) ~ 135.
if (x >= 135305999368893231589) revert("EXP_OVERFLOW");
// x is now in the range (-42, 136) * 1e18. Convert to (-42, 136) * 2**96
// for more intermediate precision and a binary basis. This base conversion
// is a multiplication by 1e18 / 2**96 = 5**18 / 2**78.
x = (x << 78) / 5**18;
// Reduce range of x to (-½ ln 2, ½ ln 2) * 2**96 by factoring out powers
// of two such that exp(x) = exp(x') * 2**k, where k is an integer.
// Solving this gives k = round(x / log(2)) and x' = x - k * log(2).
int256 k = ((x << 96) / 54916777467707473351141471128 + 2**95) >> 96;
x = x - k * 54916777467707473351141471128;
// k is in the range [-61, 195].
// Evaluate using a (6, 7)-term rational approximation.
// p is made monic, we'll multiply by a scale factor later.
int256 y = x + 1346386616545796478920950773328;
y = ((y * x) >> 96) + 57155421227552351082224309758442;
int256 p = y + x - 94201549194550492254356042504812;
p = ((p * y) >> 96) + 28719021644029726153956944680412240;
p = p * x + (4385272521454847904659076985693276 << 96);
// We leave p in 2**192 basis so we don't need to scale it back up for the division.
int256 q = x - 2855989394907223263936484059900;
q = ((q * x) >> 96) + 50020603652535783019961831881945;
q = ((q * x) >> 96) - 533845033583426703283633433725380;
q = ((q * x) >> 96) + 3604857256930695427073651918091429;
q = ((q * x) >> 96) - 14423608567350463180887372962807573;
q = ((q * x) >> 96) + 26449188498355588339934803723976023;
assembly {
// Div in assembly because solidity adds a zero check despite the unchecked.
// The q polynomial won't have zeros in the domain as all its roots are complex.
// No scaling is necessary because p is already 2**96 too large.
r := sdiv(p, q)
}
// r should be in the range (0.09, 0.25) * 2**96.
// We now need to multiply r by:
// * the scale factor s = ~6.031367120.
// * the 2**k factor from the range reduction.
// * the 1e18 / 2**96 factor for base conversion.
// We do this all at once, with an intermediate result in 2**213
// basis, so the final right shift is always by a positive amount.
r = int256((uint256(r) * 3822833074963236453042738258902158003155416615667) >> uint256(195 - k));
}
}
function lnWad(int256 x) internal pure returns (int256 r) {
unchecked {
require(x > 0, "UNDEFINED");
// We want to convert x from 10**18 fixed point to 2**96 fixed point.
// We do this by multiplying by 2**96 / 10**18. But since
// ln(x * C) = ln(x) + ln(C), we can simply do nothing here
// and add ln(2**96 / 10**18) at the end.
// Reduce range of x to (1, 2) * 2**96
// ln(2^k * x) = k * ln(2) + ln(x)
int256 k = int256(log2(uint256(x))) - 96;
x <<= uint256(159 - k);
x = int256(uint256(x) >> 159);
// Evaluate using a (8, 8)-term rational approximation.
// p is made monic, we will multiply by a scale factor later.
int256 p = x + 3273285459638523848632254066296;
p = ((p * x) >> 96) + 24828157081833163892658089445524;
p = ((p * x) >> 96) + 43456485725739037958740375743393;
p = ((p * x) >> 96) - 11111509109440967052023855526967;
p = ((p * x) >> 96) - 45023709667254063763336534515857;
p = ((p * x) >> 96) - 14706773417378608786704636184526;
p = p * x - (795164235651350426258249787498 << 96);
// We leave p in 2**192 basis so we don't need to scale it back up for the division.
// q is monic by convention.
int256 q = x + 5573035233440673466300451813936;
q = ((q * x) >> 96) + 71694874799317883764090561454958;
q = ((q * x) >> 96) + 283447036172924575727196451306956;
q = ((q * x) >> 96) + 401686690394027663651624208769553;
q = ((q * x) >> 96) + 204048457590392012362485061816622;
q = ((q * x) >> 96) + 31853899698501571402653359427138;
q = ((q * x) >> 96) + 909429971244387300277376558375;
assembly {
// Div in assembly because solidity adds a zero check despite the unchecked.
// The q polynomial is known not to have zeros in the domain.
// No scaling required because p is already 2**96 too large.
r := sdiv(p, q)
}
// r is in the range (0, 0.125) * 2**96
// Finalization, we need to:
// * multiply by the scale factor s = 5.549…
// * add ln(2**96 / 10**18)
// * add k * ln(2)
// * multiply by 10**18 / 2**96 = 5**18 >> 78
// mul s * 5e18 * 2**96, base is now 5**18 * 2**192
r *= 1677202110996718588342820967067443963516166;
// add ln(2) * k * 5e18 * 2**192
r += 16597577552685614221487285958193947469193820559219878177908093499208371 * k;
// add ln(2**96 / 10**18) * 5e18 * 2**192
r += 600920179829731861736702779321621459595472258049074101567377883020018308;
// base conversion: mul 2**18 / 2**192
r >>= 174;
}
}
/*//////////////////////////////////////////////////////////////
LOW LEVEL FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
function mulDivDown(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
assembly {
// Store x * y in z for now.
z := mul(x, y)
// Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))
if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) {
revert(0, 0)
}
// Divide z by the denominator.
z := div(z, denominator)
}
}
function mulDivUp(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
assembly {
// Store x * y in z for now.
z := mul(x, y)
// Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))
if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) {
revert(0, 0)
}
// First, divide z - 1 by the denominator and add 1.
// We allow z - 1 to underflow if z is 0, because we multiply the
// end result by 0 if z is zero, ensuring we return 0 if z is zero.
z := mul(iszero(iszero(z)), add(div(sub(z, 1), denominator), 1))
}
}
function rpow(
uint256 x,
uint256 n,
uint256 scalar
) internal pure returns (uint256 z) {
assembly {
switch x
case 0 {
switch n
case 0 {
// 0 ** 0 = 1
z := scalar
}
default {
// 0 ** n = 0
z := 0
}
}
default {
switch mod(n, 2)
case 0 {
// If n is even, store scalar in z for now.
z := scalar
}
default {
// If n is odd, store x in z for now.
z := x
}
// Shifting right by 1 is like dividing by 2.
let half := shr(1, scalar)
for {
// Shift n right by 1 before looping to halve it.
n := shr(1, n)
} n {
// Shift n right by 1 each iteration to halve it.
n := shr(1, n)
} {
// Revert immediately if x ** 2 would overflow.
// Equivalent to iszero(eq(div(xx, x), x)) here.
if shr(128, x) {
revert(0, 0)
}
// Store x squared.
let xx := mul(x, x)
// Round to the nearest number.
let xxRound := add(xx, half)
// Revert if xx + half overflowed.
if lt(xxRound, xx) {
revert(0, 0)
}
// Set x to scaled xxRound.
x := div(xxRound, scalar)
// If n is even:
if mod(n, 2) {
// Compute z * x.
let zx := mul(z, x)
// If z * x overflowed:
if iszero(eq(div(zx, x), z)) {
// Revert if x is non-zero.
if iszero(iszero(x)) {
revert(0, 0)
}
}
// Round to the nearest number.
let zxRound := add(zx, half)
// Revert if zx + half overflowed.
if lt(zxRound, zx) {
revert(0, 0)
}
// Return properly scaled zxRound.
z := div(zxRound, scalar)
}
}
}
}
}
/*//////////////////////////////////////////////////////////////
GENERAL NUMBER UTILITIES
//////////////////////////////////////////////////////////////*/
function sqrt(uint256 x) internal pure returns (uint256 z) {
assembly {
let y := x // We start y at x, which will help us make our initial estimate.
z := 181 // The "correct" value is 1, but this saves a multiplication later.
// This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
// start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.
// We check y >= 2^(k + 8) but shift right by k bits
// each branch to ensure that if x >= 256, then y >= 256.
if iszero(lt(y, 0x10000000000000000000000000000000000)) {
y := shr(128, y)
z := shl(64, z)
}
if iszero(lt(y, 0x1000000000000000000)) {
y := shr(64, y)
z := shl(32, z)
}
if iszero(lt(y, 0x10000000000)) {
y := shr(32, y)
z := shl(16, z)
}
if iszero(lt(y, 0x1000000)) {
y := shr(16, y)
z := shl(8, z)
}
// Goal was to get z*z*y within a small factor of x. More iterations could
// get y in a tighter range. Currently, we will have y in [256, 256*2^16).
// We ensured y >= 256 so that the relative difference between y and y+1 is small.
// That's not possible if x < 256 but we can just verify those cases exhaustively.
// Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
// Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
// Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.
// For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
// (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.
// Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
// sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.
// There is no overflow risk here since y < 2^136 after the first branch above.
z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.
// Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
// If x+1 is a perfect square, the Babylonian method cycles between
// floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
// See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
// Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
// If you don't care whether the floor or ceil square root is returned, you can remove this statement.
z := sub(z, lt(div(x, z), z))
}
}
function log2(uint256 x) internal pure returns (uint256 r) {
require(x > 0, "UNDEFINED");
assembly {
r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(r, shl(3, lt(0xff, shr(r, x))))
r := or(r, shl(2, lt(0xf, shr(r, x))))
r := or(r, shl(1, lt(0x3, shr(r, x))))
r := or(r, lt(0x1, shr(r, x)))
}
}
function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
assembly {
// z will equal 0 if y is 0, unlike in Solidity where it will revert.
z := mod(x, y)
}
}
function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
assembly {
// z will equal 0 if y is 0, unlike in Solidity where it will revert.
z := div(x, y)
}
}
/// @dev Will return 0 instead of reverting if y is zero.
function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
assembly {
// Add 1 to x * y if x % y > 0.
z := add(gt(mod(x, y), 0), div(x, y))
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
/// SPDX-License-Identifier: SSPL-1.-0
/**
* @custom:org.protocol='mevETH LST Protocol'
* @custom:org.security='mailto:[email protected]'
* @custom:org.vcs-commit=$GIT_COMMIT_SHA
* @custom:org.vendor='CommodityStream, Inc'
* @custom:org.schema-version="1.0"
* @custom.org.encryption="manifoldfinance.com/.well-known/pgp-key.asc"
* @custom:org.preferred-languages="en"
*/
pragma solidity ^0.8.19;
// Also a superset of ERC20 but due to some solmate <-> OZ IERC20 nastiness this interface doesn't include it
interface IERC4626 {
/// @return assetTokenAddress The address of the asset token
function asset() external view returns (address assetTokenAddress);
/// @return totalManagedAssets The amount of eth controlled by the vault
function totalAssets() external view returns (uint256 totalManagedAssets);
/// @param assets The amount of assets to convert to shares
/// @return shares The value of the given assets in shares
function convertToShares(uint256 assets) external view returns (uint256 shares);
/// @param shares The amount of shares to convert to assets
/// @return assets The value of the given shares in assets
function convertToAssets(uint256 shares) external view returns (uint256 assets);
/// @param reciever The address in question of who would be depositing, doesn't matter in this case
/// @return maxAssets The maximum amount of assets that can be deposited
function maxDeposit(address reciever) external view returns (uint256 maxAssets);
/// @param assets The amount of assets that would be deposited
/// @return shares The amount of shares that would be minted, *under ideal conditions* only
function previewDeposit(uint256 assets) external view returns (uint256 shares);
/// @param assets The amount of WETH which should be deposited
/// @param receiver The address user whom should recieve the mevEth out
/// @return shares The amount of shares minted
function deposit(uint256 assets, address receiver) external payable returns (uint256 shares);
/// @param reciever The address in question of who would be minting, doesn't matter in this case
/// @return maxShares The maximum amount of shares that can be minted
function maxMint(address reciever) external view returns (uint256 maxShares);
/// @param shares The amount of shares that would be minted
/// @return assets The amount of assets that would be required, *under ideal conditions* only
function previewMint(uint256 shares) external view returns (uint256 assets);
/// @param shares The amount of shares that should be minted
/// @param receiver The address user whom should recieve the mevEth out
/// @return assets The amount of assets deposited
function mint(uint256 shares, address receiver) external payable returns (uint256 assets);
/// @param owner The address in question of who would be withdrawing
/// @return maxAssets The maximum amount of assets that can be withdrawn
function maxWithdraw(address owner) external view returns (uint256 maxAssets);
/// @param assets The amount of assets that would be withdrawn
/// @return shares The amount of shares that would be burned, *under ideal conditions* only
function previewWithdraw(uint256 assets) external view returns (uint256 shares);
/// @param assets The amount of assets that should be withdrawn
/// @param receiver The address user whom should recieve the mevEth out
/// @param owner The address of the owner of the mevEth
/// @return shares The amount of shares burned
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
/// @param owner The address in question of who would be redeeming their shares
/// @return maxShares The maximum amount of shares they could redeem
function maxRedeem(address owner) external view returns (uint256 maxShares);
/// @param shares The amount of shares that would be burned
/// @return assets The amount of assets that would be withdrawn, *under ideal conditions* only
function previewRedeem(uint256 shares) external view returns (uint256 assets);
/// @param shares The amount of shares that should be burned
/// @param receiver The address user whom should recieve the wETH out
/// @param owner The address of the owner of the mevEth
/// @return assets The amount of assets withdrawn
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
/**
* @dev Emitted when a deposit is made, either through mint or deposit
*/
event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);
/**
* @dev Emitted when a withdrawal is made, either through redeem or withdraw
*/
event Withdraw(address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares);
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {ERC20} from "./ERC20.sol";
import {SafeTransferLib} from "../utils/SafeTransferLib.sol";
/// @notice Minimalist and modern Wrapped Ether implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/WETH.sol)
/// @author Inspired by WETH9 (https://github.com/dapphub/ds-weth/blob/master/src/weth9.sol)
contract WETH is ERC20("Wrapped Ether", "WETH", 18) {
using SafeTransferLib for address;
event Deposit(address indexed from, uint256 amount);
event Withdrawal(address indexed to, uint256 amount);
function deposit() public payable virtual {
_mint(msg.sender, msg.value);
emit Deposit(msg.sender, msg.value);
}
function withdraw(uint256 amount) public virtual {
_burn(msg.sender, amount);
emit Withdrawal(msg.sender, amount);
msg.sender.safeTransferETH(amount);
}
receive() external payable virtual {
deposit();
}
}
/// SPDX-License-Identifier: SSPL-1.-0
/**
* @custom:org.protocol='mevETH LST Protocol'
* @custom:org.security='mailto:[email protected]'
* @custom:org.vcs-commit=$GIT_COMMIT_SHA
* @custom:org.vendor='CommodityStream, Inc'
* @custom:org.schema-version="1.0"
* @custom.org.encryption="manifoldfinance.com/.well-known/pgp-key.asc"
* @custom:org.preferred-languages="en"
*/
pragma solidity ^0.8.19;
interface MevEthErrors {
/// Errors
error StakingPaused();
error NotEnoughEth();
error ZeroValue();
error InvalidOperator();
error DepositTooSmall();
error InvalidSender();
error PrematureStakingModuleUpdateFinalization();
error PrematureMevEthShareVaultUpdateFinalization();
error InvalidPendingStakingModule();
error InvalidPendingMevEthShareVault();
error TransferExceedsAllowance();
error TransferFailed();
error ZeroAddress();
error AlreadyInitialized();
error SendError();
error FeesTooHigh();
error WrongDepositAmount();
error WrongWithdrawAmount();
error UnAuthorizedCaller();
error WithdrawTooSmall();
error NotFinalised();
error AlreadyClaimed();
error AlreadyFinalised();
error IndexExceedsQueueLength();
error DepositWasFrontrun();
error SandwichProtection();
error NonZeroVaultBalance();
error AlreadyDeposited();
error IncorrectWithdrawalCredentials();
}
/// SPDX-License-Identifier: SSPL-1.-0
/**
* @custom:org.protocol='mevETH LST Protocol'
* @custom:org.security='mailto:[email protected]'
* @custom:org.vcs-commit=$GIT_COMMIT_SHA
* @custom:org.vendor='CommodityStream, Inc'
* @custom:org.schema-version="1.0"
* @custom.org.encryption="manifoldfinance.com/.well-known/pgp-key.asc"
* @custom:org.preferred-languages="en"
*/
pragma solidity ^0.8.19;
interface IStakingModule {
/**
* @dev Structure for passing information about the validator deposit data.
* @param operator - address of the operator.
* @param pubkey - BLS public key of the validator, generated by the operator.
* @param withdrawal_credentials - withdrawal credentials used for generating the deposit data.
* @param signature - BLS signature of the validator, generated by the operator.
* @param deposit_data_root - hash tree root of the deposit data, generated by the operator.
*/
struct ValidatorData {
address operator;
bytes pubkey;
bytes32 withdrawal_credentials;
bytes signature;
bytes32 deposit_data_root; // more efficient to be calculated off-chain
}
/**
* @dev Allows users to deposit funds into the contract.
* @param data ValidatorData calldata containing the validator's public key, withdrawal credentials, and amount of tokens to be deposited.
* @param latestDepositRoot bytes32 containing the latest deposit root.
*/
function deposit(ValidatorData calldata data, bytes32 latestDepositRoot) external payable;
function validators() external view returns (uint256);
function mevEth() external view returns (address);
/**
* @notice VALIDATOR_DEPOSIT_SIZE()
*
* This function returns the size of the validator deposit.
*
* @dev This function is used to determine the size of the validator deposit. It is used to ensure that validators have the correct amount of funds in order
* to participate in the network.
*/
function VALIDATOR_DEPOSIT_SIZE() external view returns (uint256);
// onlyAdmin Functions
/**
* @notice This function is used to pay rewards to the users.
* @dev This function is used to pay rewards to the users. It takes in a uint256 rewards parameter which is the amount of rewards to be paid.
*/
function payRewards(uint256 rewards) external;
/**
* @notice This function allows a validator to withdraw their rewards from the contract.
* @dev This function is called by a validator to withdraw their rewards from the contract. It will transfer the rewards to the validator's address.
*/
function payValidatorWithdraw() external;
function recoverToken(address token, address recipient, uint256 amount) external;
/**
* @notice record() function is used to record the data in the smart contract.
* @dev record() function takes no parameters and returns four uint128 values.
*/
function record() external returns (uint128, uint128, uint128, uint128);
/**
* @notice registerExit() allows users to exit the system.
* @dev registerExit() is a function that allows users to exit the system. It is triggered by an external call.
*/
function registerExit() external;
function batchMigrate(IStakingModule.ValidatorData[] calldata batchData) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
interface IERC20Burnable {
function burnFrom(address account, uint256 amount) external;
}
/// SPDX-License-Identifier: SSPL-1.-0
/**
* @custom:org.protocol='mevETH LST Protocol'
* @custom:org.security='mailto:[email protected]'
* @custom:org.vcs-commit=$GIT_COMMIT_SHA
* @custom:org.vendor='CommodityStream, Inc'
* @custom:org.schema-version="1.0"
* @custom.org.encryption="manifoldfinance.com/.well-known/pgp-key.asc"
* @custom:org.preferred-languages="en"
*/
pragma solidity ^0.8.19;
/// @title TinyMevEth
/// @notice smol interface for interacting with MevEth
interface ITinyMevEth {
/**
* @dev Function to grant rewards to other users.
* @notice This function is payable and should be called with the amount of rewards to be granted.
*/
function grantRewards() external payable;
/**
* @dev Function to allow a validator to withdraw funds from the contract.
* @notice This function must be called with a validator address and a payable amount.
*/
function grantValidatorWithdraw() external payable;
}