// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library AddressUpgradeable {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.21;
import { IEigenpieConfig } from "../utils/EigenpieConfigRoleChecker.sol";
import { INodeDelegator } from "../interfaces/INodeDelegator.sol";
import { EigenpieConstants } from "../utils/EigenpieConstants.sol";
import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IStrategyManager } from "../interfaces/eigenlayer/IStrategyManager.sol";
import { IStrategy } from "../interfaces/eigenlayer/IStrategy.sol";
import { IEigenPodManager } from "../interfaces/eigenlayer/IEigenPodManager.sol";
import { TransferHelper } from "../utils/TransferHelper.sol";
import { IMLRT } from "../interfaces/IMLRT.sol";
import { IMintableERC20 } from "../interfaces/IMintableERC20.sol";
import "../utils/UtilLib.sol";
library AssetManagementLib {
using SafeERC20 for IERC20;
event AssetDepositIntoStrategy(address indexed asset, address indexed strategy, uint256 depositAmount);
function getAssetBalances(
address eigenlayerStrategyManagerAddress,
uint256 stakedButNotVerifiedEth,
IEigenpieConfig eigenpieConfig
)
external
view
returns (address[] memory assets, uint256[] memory assetBalances)
{
IStrategyManager strategyManager = IStrategyManager(eigenlayerStrategyManagerAddress);
(IStrategy[] memory strategies,) = strategyManager.getDeposits(address(this));
uint256 strategiesLength = strategies.length;
assets = new address[](strategiesLength + 1); // LSTs and native
assetBalances = new uint256[](strategiesLength + 1); // LSTs and native
IEigenPodManager eigenPodManager = getEigenPodManager(eigenpieConfig);
assets[0] = EigenpieConstants.PLATFORM_TOKEN_ADDRESS;
assetBalances[0] = getEthBalance(eigenPodManager, stakedButNotVerifiedEth, address(this));
for (uint256 i = 0; i < strategiesLength;) {
assets[i + 1] = address(IStrategy(strategies[i]).underlyingToken());
assetBalances[i + 1] = IStrategy(strategies[i]).userUnderlyingView(address(this));
unchecked {
++i;
}
}
return (assets, assetBalances);
}
function getAssetBalance(
IEigenpieConfig eigenpieConfig,
address asset,
uint256 stakedButNotVerifiedEth
)
external
view
returns (uint256)
{
if (UtilLib.isNativeToken(asset)) {
IEigenPodManager eigenPodManager = getEigenPodManager(eigenpieConfig);
return getEthBalance(eigenPodManager, stakedButNotVerifiedEth, address(this));
} else {
address strategy = eigenpieConfig.assetStrategy(asset);
if (strategy == address(0)) {
return 0;
}
return IStrategy(strategy).userUnderlyingView(address(this));
}
}
function getEthBalance(
IEigenPodManager eigenPodManager,
uint256 stakedButNotVerifiedEth,
address nodeDelegator
)
public
view
returns (uint256)
{
int256 podOwnerShares = eigenPodManager.podOwnerShares(nodeDelegator);
return podOwnerShares < 0
? stakedButNotVerifiedEth - uint256(-podOwnerShares)
: stakedButNotVerifiedEth + uint256(podOwnerShares);
}
function getEigenPodManager(IEigenpieConfig eigenpieConfig) public view returns (IEigenPodManager) {
return IEigenPodManager(eigenpieConfig.getContract(EigenpieConstants.EIGENPOD_MANAGER));
}
function calculateRefundGas(uint256 adminGasSpentInWei) external returns (uint256) {
uint256 gasRefund = msg.value >= adminGasSpentInWei ? adminGasSpentInWei : msg.value;
return gasRefund;
}
function calculateAndTransferRefundGas(address origin, uint256 adminGasSpentInWei) external returns (uint256) {
uint256 gasRefund = msg.value >= adminGasSpentInWei ? adminGasSpentInWei : msg.value;
transferRefundGas(origin, gasRefund);
return gasRefund;
}
function transferRefundGas(address origin, uint256 refundGas) internal {
TransferHelper.safeTransferETH(origin, refundGas);
}
function calculateGasSpent(
uint256 initialGas,
IEigenpieConfig eigenpieConfig,
uint256 gasPrice
)
external
returns (uint256)
{
uint256 baseGasAmountSpent = eigenpieConfig.baseGasAmountSpent();
uint256 gasSpent = (initialGas - gasleft() + baseGasAmountSpent) * gasPrice;
return gasSpent;
}
function depositAssetIntoStrategy(
address asset,
IEigenpieConfig eigenpieConfig,
uint256 lstAmount,
bool isClient
)
external
{
address strategy = eigenpieConfig.assetStrategy(asset);
if (strategy == address(0)) {
revert INodeDelegator.StrategyIsNotSetForAsset();
}
IERC20 token = IERC20(asset);
address eigenlayerStrategyManagerAddress = eigenpieConfig.getContract(EigenpieConstants.EIGEN_STRATEGY_MANAGER);
// Transfer tokens from sender to this contract if lstAmount is specified
if (lstAmount > 0) {
token.safeTransferFrom(msg.sender, address(this), lstAmount);
}
uint256 depositAmount = isClient ? lstAmount : token.balanceOf(address(this));
emit INodeDelegator.AssetDepositIntoStrategy(asset, strategy, depositAmount);
if (depositAmount != 0) {
uint256 oldAllowance = token.allowance(address(this), eigenlayerStrategyManagerAddress);
if (oldAllowance < depositAmount) {
if (oldAllowance > 0) {
// Reset approval to 0 if there is any existing allowance
token.safeApprove(eigenlayerStrategyManagerAddress, 0);
}
// Approve the new allowance
token.safeApprove(eigenlayerStrategyManagerAddress, depositAmount);
}
IStrategyManager(eigenlayerStrategyManagerAddress).depositIntoStrategy(
IStrategy(strategy), token, depositAmount
);
}
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "./Merkle.sol";
import "./Endian.sol";
//Utility library for parsing and PHASE0 beacon chain block headers
//SSZ Spec: https://github.com/ethereum/consensus-specs/blob/dev/ssz/simple-serialize.md#merkleization
//BeaconBlockHeader Spec:
// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconblockheader
//BeaconState Spec: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconstate
library BeaconChainProofs {
/// @notice Heights of various merkle trees in the beacon chain
/// - beaconBlockRoot
/// | HEIGHT: BEACON_BLOCK_HEADER_TREE_HEIGHT
/// -- beaconStateRoot
/// | HEIGHT: BEACON_STATE_TREE_HEIGHT
/// validatorContainerRoot, balanceContainerRoot
/// | | HEIGHT: BALANCE_TREE_HEIGHT
/// | individual balances
/// | HEIGHT: VALIDATOR_TREE_HEIGHT
/// individual validators
uint256 internal constant BEACON_BLOCK_HEADER_TREE_HEIGHT = 3;
uint256 internal constant BEACON_STATE_TREE_HEIGHT = 5;
uint256 internal constant BALANCE_TREE_HEIGHT = 38;
uint256 internal constant VALIDATOR_TREE_HEIGHT = 40;
/// @notice Index of the beaconStateRoot in the `BeaconBlockHeader` container
///
/// BeaconBlockHeader = [..., state_root, ...]
/// 0... 3
///
/// (See https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconblockheader)
uint256 internal constant STATE_ROOT_INDEX = 3;
/// @notice Indices for fields in the `BeaconState` container
///
/// BeaconState = [..., validators, balances, ...]
/// 0... 11 12
///
/// (See https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#beaconstate)
uint256 internal constant VALIDATOR_CONTAINER_INDEX = 11;
uint256 internal constant BALANCE_CONTAINER_INDEX = 12;
/// @notice Number of fields in the `Validator` container
/// (See https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator)
uint256 internal constant VALIDATOR_FIELDS_LENGTH = 8;
/// @notice Indices for fields in the `Validator` container
uint256 internal constant VALIDATOR_PUBKEY_INDEX = 0;
uint256 internal constant VALIDATOR_WITHDRAWAL_CREDENTIALS_INDEX = 1;
uint256 internal constant VALIDATOR_BALANCE_INDEX = 2;
uint256 internal constant VALIDATOR_SLASHED_INDEX = 3;
uint256 internal constant VALIDATOR_EXIT_EPOCH_INDEX = 6;
/// @notice Slot/Epoch timings
uint64 internal constant SECONDS_PER_SLOT = 12;
uint64 internal constant SLOTS_PER_EPOCH = 32;
uint64 internal constant SECONDS_PER_EPOCH = SLOTS_PER_EPOCH * SECONDS_PER_SLOT;
/// @notice `FAR_FUTURE_EPOCH` is used as the default value for certain `Validator`
/// fields when a `Validator` is first created on the beacon chain
uint64 internal constant FAR_FUTURE_EPOCH = type(uint64).max;
bytes8 internal constant UINT64_MASK = 0xffffffffffffffff;
/// @notice Contains a beacon state root and a merkle proof verifying its inclusion under a beacon block root
struct StateRootProof {
bytes32 beaconStateRoot;
bytes proof;
}
/// @notice Contains a validator's fields and a merkle proof of their inclusion under a beacon state root
struct ValidatorProof {
bytes32[] validatorFields;
bytes proof;
}
/// @notice Contains a beacon balance container root and a proof of this root under a beacon block root
struct BalanceContainerProof {
bytes32 balanceContainerRoot;
bytes proof;
}
/// @notice Contains a validator balance root and a proof of its inclusion under a balance container root
struct BalanceProof {
bytes32 pubkeyHash;
bytes32 balanceRoot;
bytes proof;
}
/**
*
* VALIDATOR FIELDS -> BEACON STATE ROOT -> BEACON BLOCK ROOT
*
*/
/// @notice Verify a merkle proof of the beacon state root against a beacon block root
/// @param beaconBlockRoot merkle root of the beacon block
/// @param proof the beacon state root and merkle proof of its inclusion under `beaconBlockRoot`
function verifyStateRoot(bytes32 beaconBlockRoot, StateRootProof calldata proof) internal view {
require(
proof.proof.length == 32 * (BEACON_BLOCK_HEADER_TREE_HEIGHT),
"BeaconChainProofs.verifyStateRoot: Proof has incorrect length"
);
/// This merkle proof verifies the `beaconStateRoot` under the `beaconBlockRoot`
/// - beaconBlockRoot
/// | HEIGHT: BEACON_BLOCK_HEADER_TREE_HEIGHT
/// -- beaconStateRoot
require(
Merkle.verifyInclusionSha256({
proof: proof.proof,
root: beaconBlockRoot,
leaf: proof.beaconStateRoot,
index: STATE_ROOT_INDEX
}),
"BeaconChainProofs.verifyStateRoot: Invalid state root merkle proof"
);
}
/// @notice Verify a merkle proof of a validator container against a `beaconStateRoot`
/// @dev This proof starts at a validator's container root, proves through the validator container root,
/// and continues proving to the root of the `BeaconState`
/// @dev See https://eth2book.info/capella/part3/containers/dependencies/#validator for info on `Validator`
/// containers
/// @dev See https://eth2book.info/capella/part3/containers/state/#beaconstate for info on `BeaconState` containers
/// @param beaconStateRoot merkle root of the `BeaconState` container
/// @param validatorFields an individual validator's fields. These are merklized to form a `validatorRoot`,
/// which is used as the leaf to prove against `beaconStateRoot`
/// @param validatorFieldsProof a merkle proof of inclusion of `validatorFields` under `beaconStateRoot`
/// @param validatorIndex the validator's unique index
function verifyValidatorFields(
bytes32 beaconStateRoot,
bytes32[] calldata validatorFields,
bytes calldata validatorFieldsProof,
uint40 validatorIndex
)
internal
view
{
require(
validatorFields.length == VALIDATOR_FIELDS_LENGTH,
"BeaconChainProofs.verifyValidatorFields: Validator fields has incorrect length"
);
/// Note: the reason we use `VALIDATOR_TREE_HEIGHT + 1` here is because the merklization process for
/// this container includes hashing the root of the validator tree with the length of the validator list
require(
validatorFieldsProof.length == 32 * ((VALIDATOR_TREE_HEIGHT + 1) + BEACON_STATE_TREE_HEIGHT),
"BeaconChainProofs.verifyValidatorFields: Proof has incorrect length"
);
// Merkleize `validatorFields` to get the leaf to prove
bytes32 validatorRoot = Merkle.merkleizeSha256(validatorFields);
/// This proof combines two proofs, so its index accounts for the relative position of leaves in two trees:
/// - beaconStateRoot
/// | HEIGHT: BEACON_STATE_TREE_HEIGHT
/// -- validatorContainerRoot
/// | HEIGHT: VALIDATOR_TREE_HEIGHT + 1
/// ---- validatorRoot
uint256 index = (VALIDATOR_CONTAINER_INDEX << (VALIDATOR_TREE_HEIGHT + 1)) | uint256(validatorIndex);
require(
Merkle.verifyInclusionSha256({
proof: validatorFieldsProof,
root: beaconStateRoot,
leaf: validatorRoot,
index: index
}),
"BeaconChainProofs.verifyValidatorFields: Invalid merkle proof"
);
}
/**
*
* VALIDATOR BALANCE -> BALANCE CONTAINER ROOT -> BEACON BLOCK ROOT
*
*/
/// @notice Verify a merkle proof of the beacon state's balances container against the beacon block root
/// @dev This proof starts at the balance container root, proves through the beacon state root, and
/// continues proving through the beacon block root. As a result, this proof will contain elements
/// of a `StateRootProof` under the same block root, with the addition of proving the balances field
/// within the beacon state.
/// @dev This is used to make checkpoint proofs more efficient, as a checkpoint will verify multiple balances
/// against the same balance container root.
/// @param beaconBlockRoot merkle root of the beacon block
/// @param proof a beacon balance container root and merkle proof of its inclusion under `beaconBlockRoot`
function verifyBalanceContainer(bytes32 beaconBlockRoot, BalanceContainerProof calldata proof) internal view {
require(
proof.proof.length == 32 * (BEACON_BLOCK_HEADER_TREE_HEIGHT + BEACON_STATE_TREE_HEIGHT),
"BeaconChainProofs.verifyBalanceContainer: Proof has incorrect length"
);
/// This proof combines two proofs, so its index accounts for the relative position of leaves in two trees:
/// - beaconBlockRoot
/// | HEIGHT: BEACON_BLOCK_HEADER_TREE_HEIGHT
/// -- beaconStateRoot
/// | HEIGHT: BEACON_STATE_TREE_HEIGHT
/// ---- balancesContainerRoot
uint256 index = (STATE_ROOT_INDEX << (BEACON_STATE_TREE_HEIGHT)) | BALANCE_CONTAINER_INDEX;
require(
Merkle.verifyInclusionSha256({
proof: proof.proof,
root: beaconBlockRoot,
leaf: proof.balanceContainerRoot,
index: index
}),
"BeaconChainProofs.verifyBalanceContainer: invalid balance container proof"
);
}
/// @notice Verify a merkle proof of a validator's balance against the beacon state's `balanceContainerRoot`
/// @param balanceContainerRoot the merkle root of all validators' current balances
/// @param validatorIndex the index of the validator whose balance we are proving
/// @param proof the validator's associated balance root and a merkle proof of inclusion under
/// `balanceContainerRoot`
/// @return validatorBalanceGwei the validator's current balance (in gwei)
function verifyValidatorBalance(
bytes32 balanceContainerRoot,
uint40 validatorIndex,
BalanceProof calldata proof
)
internal
view
returns (uint64 validatorBalanceGwei)
{
/// Note: the reason we use `BALANCE_TREE_HEIGHT + 1` here is because the merklization process for
/// this container includes hashing the root of the balances tree with the length of the balances list
require(
proof.proof.length == 32 * (BALANCE_TREE_HEIGHT + 1),
"BeaconChainProofs.verifyValidatorBalance: Proof has incorrect length"
);
/// When merkleized, beacon chain balances are combined into groups of 4 called a `balanceRoot`. The merkle
/// proof here verifies that this validator's `balanceRoot` is included in the `balanceContainerRoot`
/// - balanceContainerRoot
/// | HEIGHT: BALANCE_TREE_HEIGHT
/// -- balanceRoot
uint256 balanceIndex = uint256(validatorIndex / 4);
require(
Merkle.verifyInclusionSha256({
proof: proof.proof,
root: balanceContainerRoot,
leaf: proof.balanceRoot,
index: balanceIndex
}),
"BeaconChainProofs.verifyValidatorBalance: Invalid merkle proof"
);
/// Extract the individual validator's balance from the `balanceRoot`
return getBalanceAtIndex(proof.balanceRoot, validatorIndex);
}
/**
* @notice Parses a balanceRoot to get the uint64 balance of a validator.
* @dev During merkleization of the beacon state balance tree, four uint64 values are treated as a single
* leaf in the merkle tree. We use validatorIndex % 4 to determine which of the four uint64 values to
* extract from the balanceRoot.
* @param balanceRoot is the combination of 4 validator balances being proven for
* @param validatorIndex is the index of the validator being proven for
* @return The validator's balance, in Gwei
*/
function getBalanceAtIndex(bytes32 balanceRoot, uint40 validatorIndex) internal pure returns (uint64) {
uint256 bitShiftAmount = (validatorIndex % 4) * 64;
return Endian.fromLittleEndianUint64(bytes32((uint256(balanceRoot) << bitShiftAmount)));
}
/// @notice Indices for fields in the `Validator` container:
/// 0: pubkey
/// 1: withdrawal credentials
/// 2: effective balance
/// 3: slashed?
/// 4: activation elligibility epoch
/// 5: activation epoch
/// 6: exit epoch
/// 7: withdrawable epoch
///
/// (See https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator)
/// @dev Retrieves a validator's pubkey hash
function getPubkeyHash(bytes32[] memory validatorFields) internal pure returns (bytes32) {
return validatorFields[VALIDATOR_PUBKEY_INDEX];
}
/// @dev Retrieves a validator's withdrawal credentials
function getWithdrawalCredentials(bytes32[] memory validatorFields) internal pure returns (bytes32) {
return validatorFields[VALIDATOR_WITHDRAWAL_CREDENTIALS_INDEX];
}
/// @dev Retrieves a validator's effective balance (in gwei)
function getEffectiveBalanceGwei(bytes32[] memory validatorFields) internal pure returns (uint64) {
return Endian.fromLittleEndianUint64(validatorFields[VALIDATOR_BALANCE_INDEX]);
}
/// @dev Retrieves true IFF a validator is marked slashed
function isValidatorSlashed(bytes32[] memory validatorFields) internal pure returns (bool) {
return validatorFields[VALIDATOR_SLASHED_INDEX] != 0;
}
/// @dev Retrieves a validator's exit epoch
function getExitEpoch(bytes32[] memory validatorFields) internal pure returns (uint64) {
return Endian.fromLittleEndianUint64(validatorFields[VALIDATOR_EXIT_EPOCH_INDEX]);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
import "../proxy/utils/Initializable.sol";
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.21;
import { UtilLib } from "./UtilLib.sol";
import { EigenpieConstants } from "./EigenpieConstants.sol";
import { IEigenpieConfig } from "../interfaces/IEigenpieConfig.sol";
import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol";
/// @title EigenpieConfigRoleChecker - Eigenpie Config Role Checker Contract
/// @notice Handles Eigenpie config role checks
abstract contract EigenpieConfigRoleChecker {
IEigenpieConfig public eigenpieConfig;
uint256[49] private __gap; // reserve for upgrade
// events
event UpdatedEigenpieConfig(address indexed eigenpieConfig);
// modifiers
modifier onlyRole(bytes32 role) {
if (!IAccessControl(address(eigenpieConfig)).hasRole(role, msg.sender)) {
string memory roleStr = string(abi.encodePacked(role));
revert IEigenpieConfig.CallerNotEigenpieConfigAllowedRole(roleStr);
}
_;
}
modifier onlyEigenpieManager() {
if (!IAccessControl(address(eigenpieConfig)).hasRole(EigenpieConstants.MANAGER, msg.sender)) {
revert IEigenpieConfig.CallerNotEigenpieConfigManager();
}
_;
}
modifier onlyPriceProvider() {
if (!IAccessControl(address(eigenpieConfig)).hasRole(EigenpieConstants.PRICE_PROVIDER_ROLE, msg.sender)) {
revert IEigenpieConfig.CallerNotEigenpieConfigPriceProvider();
}
_;
}
modifier onlyDefaultAdmin() {
if (!IAccessControl(address(eigenpieConfig)).hasRole(EigenpieConstants.DEFAULT_ADMIN_ROLE, msg.sender)) {
revert IEigenpieConfig.CallerNotEigenpieConfigAdmin();
}
_;
}
modifier onlyOracleAdmin() {
if (!IAccessControl(address(eigenpieConfig)).hasRole(EigenpieConstants.ORACLE_ADMIN_ROLE, msg.sender)) {
revert IEigenpieConfig.CallerNotEigenpieConfigOracleAdmin();
}
_;
}
modifier onlyOracle() {
if (!IAccessControl(address(eigenpieConfig)).hasRole(EigenpieConstants.ORACLE_ROLE, msg.sender)) {
revert IEigenpieConfig.CallerNotEigenpieConfigOracle();
}
_;
}
modifier onlyMinter() {
if (!IAccessControl(address(eigenpieConfig)).hasRole(EigenpieConstants.MINTER_ROLE, msg.sender)) {
revert IEigenpieConfig.CallerNotEigenpieConfigMinter();
}
_;
}
modifier onlyBurner() {
if (!IAccessControl(address(eigenpieConfig)).hasRole(EigenpieConstants.BURNER_ROLE, msg.sender)) {
revert IEigenpieConfig.CallerNotEigenpieConfigBurner();
}
_;
}
modifier onlySupportedAsset(address asset) {
if (!eigenpieConfig.isSupportedAsset(asset)) {
revert IEigenpieConfig.AssetNotSupported();
}
_;
}
modifier onlyAllowedBot() {
if (!IAccessControl(address(eigenpieConfig)).hasRole(EigenpieConstants.ALLOWED_BOT_ROLE, msg.sender)) {
revert IEigenpieConfig.CallerNotEigenpieConfigAllowedBot();
}
_;
}
// setters
/// @notice Updates the Eigenpie config contract
/// @dev only callable by Eigenpie default
/// @param eigenpieConfigAddr the new Eigenpie config contract Address
function updateEigenpieConfig(address eigenpieConfigAddr) external virtual onlyDefaultAdmin {
UtilLib.checkNonZeroAddress(eigenpieConfigAddr);
eigenpieConfig = IEigenpieConfig(eigenpieConfigAddr);
emit UpdatedEigenpieConfig(eigenpieConfigAddr);
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.21;
library EigenpieConstants {
//contracts
bytes32 public constant EIGENPIE_STAKING = keccak256("EIGENPIE_STAKING");
bytes32 public constant EIGEN_STRATEGY_MANAGER = keccak256("EIGEN_STRATEGY_MANAGER");
bytes32 public constant EIGEN_DELEGATION_MANAGER = keccak256("EIGEN_DELEGATION_MANAGER");
bytes32 public constant PRICE_PROVIDER = keccak256("PRICE_PROVIDER");
bytes32 public constant BEACON_DEPOSIT = keccak256("BEACON_DEPOSIT");
bytes32 public constant EIGENPOD_MANAGER = keccak256("EIGENPOD_MANAGER");
bytes32 public constant EIGENPIE_PREDEPOSITHELPER = keccak256("EIGENPIE_PREDEPOSITHELPER");
bytes32 public constant EIGENPIE_REWADR_DISTRIBUTOR = keccak256("EIGENPIE_REWADR_DISTRIBUTOR");
bytes32 public constant EIGENPIE_DWR = keccak256("EIGENPIE_DWR");
bytes32 public constant SSVNETWORK_ENTRY = keccak256("SSVNETWORK_ENTRY");
bytes32 public constant SSV_TOKEN = keccak256("SSV_TOKEN");
//Roles
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
bytes32 public constant MANAGER = keccak256("MANAGER");
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
bytes32 public constant ORACLE_ROLE = keccak256("ORACLE_ROLE");
bytes32 public constant ORACLE_ADMIN_ROLE = keccak256("ORACLE_ADMIN_ROLE");
bytes32 public constant PRICE_PROVIDER_ROLE = keccak256("PRICE_PROVIDER_ROLE");
bytes32 public constant ALLOWED_BOT_ROLE = keccak256("ALLOWED_BOT_ROLE");
// For Native Restaking
uint256 constant PUBKEY_LENGTH = 48;
uint256 constant SIGNATURE_LENGTH = 96;
uint256 constant MAX_VALIDATORS = 100;
uint256 constant DEPOSIT_AMOUNT = 32 ether;
uint256 constant GWEI_TO_WEI = 1e9;
uint256 public constant DENOMINATOR = 10_000;
address public constant PLATFORM_TOKEN_ADDRESS = 0xeFEfeFEfeFeFEFEFEfefeFeFefEfEfEfeFEFEFEf;
bytes32 public constant EIGENPIE_WITHDRAW_MANAGER = keccak256("EIGENPIE_WITHDRAW_MANAGER");
// External Defi
bytes32 public constant ZIRCUIT_ZSTAKIGPOOL = keccak256("ZIRCUIT_ZSTAKIGPOOL");
bytes32 public constant SWELL_SIMPLE_STAKING = keccak256("SWELL_SIMPLE_STAKING");
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
library Endian {
/**
* @notice Converts a little endian-formatted uint64 to a big endian-formatted uint64
* @param lenum little endian-formatted uint64 input, provided as 'bytes32' type
* @return n The big endian-formatted uint64
* @dev Note that the input is formatted as a 'bytes32' type (i.e. 256 bits), but it is immediately truncated to a
* uint64 (i.e. 64 bits)
* through a right-shift/shr operation.
*/
function fromLittleEndianUint64(bytes32 lenum) internal pure returns (uint64 n) {
// the number needs to be stored in little-endian encoding (ie in bytes 0-8)
n = uint64(uint256(lenum >> 192));
return (n >> 56) | ((0x00FF000000000000 & n) >> 40) | ((0x0000FF0000000000 & n) >> 24)
| ((0x000000FF00000000 & n) >> 8) | ((0x00000000FF000000 & n) << 8) | ((0x0000000000FF0000 & n) << 24)
| ((0x000000000000FF00 & n) << 40) | ((0x00000000000000FF & n) << 56);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
pragma solidity ^0.8.0;
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControl {
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*
* _Available since v3.1._
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) external;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)
pragma solidity ^0.8.0;
/**
* @dev This is the interface that {BeaconProxy} expects of its beacon.
*/
interface IBeacon {
/**
* @dev Must return an address that can be used as a delegate call target.
*
* {BeaconProxy} will check that this address is a contract.
*/
function implementation() external view returns (address);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.21;
/**
* @title IBeaconDepositContract
* @notice This is the Ethereum 2.0 deposit contract interface.
* @dev Implementation can be found here:
* https://github.com/ethereum/consensus-specs/blob/dev/solidity_deposit_contract/deposit_contract.sol
*/
interface IBeaconDepositContract {
/**
* @notice A processed deposit event.
*/
event DepositEvent(bytes pubkey, bytes withdrawal_credentials, bytes amount, bytes signature, bytes index);
/**
* @notice Submit a Phase 0 DepositData object.
* @param pubkey A BLS12-381 public key.
* @param withdrawal_credentials Commitment to a public key for withdrawals.
* @param signature A BLS12-381 signature.
* @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object, used as a protection against
* malformed input.
*/
function deposit(
bytes calldata pubkey,
bytes calldata withdrawal_credentials,
bytes calldata signature,
bytes32 deposit_data_root
)
external
payable;
/**
* @notice Query the current deposit root hash.
* @return The deposit root hash.
*/
function get_deposit_root() external view returns (bytes32);
/**
* @notice Query the current deposit count.
* @return The deposit count encoded as a little endian 64-bit number.
*/
function get_deposit_count() external view returns (bytes memory);
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;
import "./IStrategy.sol";
import "./ISignatureUtils.sol";
/**
* @title DelegationManager
* @author Layr Labs, Inc.
* @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
* @notice This is the contract for delegation in EigenLayer. The main functionalities of this contract are
* - enabling anyone to register as an operator in EigenLayer
* - allowing operators to specify parameters related to stakers who delegate to them
* - enabling any staker to delegate its stake to the operator of its choice (a given staker can only delegate to a
* single operator at a time)
* - enabling a staker to undelegate its assets from the operator it is delegated to (performed as part of the
* withdrawal process, initiated through the StrategyManager)
*/
interface IDelegationManager is ISignatureUtils {
// @notice Struct used for storing information about a single operator who has registered with EigenLayer
struct OperatorDetails {
// @notice address to receive the rewards that the operator earns via serving applications built on EigenLayer.
address earningsReceiver;
/**
* @notice Address to verify signatures when a staker wishes to delegate to the operator, as well as controlling
* "forced undelegations".
* @dev Signature verification follows these rules:
* 1) If this address is left as address(0), then any staker will be free to delegate to the operator, i.e. no
* signature verification will be performed.
* 2) If this address is an EOA (i.e. it has no code), then we follow standard ECDSA signature verification for
* delegations to the operator.
* 3) If this address is a contract (i.e. it has code) then we forward a call to the contract and verify that it
* returns the correct EIP-1271 "magic value".
*/
address delegationApprover;
/**
* @notice A minimum delay -- measured in blocks -- enforced between:
* 1) the operator signalling their intent to register for a service, via calling `Slasher.optIntoSlashing`
* and
* 2) the operator completing registration for the service, via the service ultimately calling
* `Slasher.recordFirstStakeUpdate`
* @dev note that for a specific operator, this value *cannot decrease*, i.e. if the operator wishes to modify
* their OperatorDetails,
* then they are only allowed to either increase this value or keep it the same.
*/
uint32 stakerOptOutWindowBlocks;
}
/**
* @notice Abstract struct used in calculating an EIP712 signature for a staker to approve that they (the staker
* themselves) delegate to a specific operator.
* @dev Used in computing the `STAKER_DELEGATION_TYPEHASH` and as a reference in the computation of the
* stakerDigestHash in the `delegateToBySignature` function.
*/
struct StakerDelegation {
// the staker who is delegating
address staker;
// the operator being delegated to
address operator;
// the staker's nonce
uint256 nonce;
// the expiration timestamp (UTC) of the signature
uint256 expiry;
}
/**
* @notice Abstract struct used in calculating an EIP712 signature for an operator's delegationApprover to approve
* that a specific staker delegate to the operator.
* @dev Used in computing the `DELEGATION_APPROVAL_TYPEHASH` and as a reference in the computation of the
* approverDigestHash in the `_delegate` function.
*/
struct DelegationApproval {
// the staker who is delegating
address staker;
// the operator being delegated to
address operator;
// the operator's provided salt
bytes32 salt;
// the expiration timestamp (UTC) of the signature
uint256 expiry;
}
/**
* Struct type used to specify an existing queued withdrawal. Rather than storing the entire struct, only a hash is
* stored.
* In functions that operate on existing queued withdrawals -- e.g. completeQueuedWithdrawal`, the data is
* resubmitted and the hash of the submitted
* data is computed by `calculateWithdrawalRoot` and checked against the stored hash in order to confirm the
* integrity of the submitted data.
*/
struct Withdrawal {
// The address that originated the Withdrawal
address staker;
// The address that the staker was delegated to at the time that the Withdrawal was created
address delegatedTo;
// The address that can complete the Withdrawal + will receive funds when completing the withdrawal
address withdrawer;
// Nonce used to guarantee that otherwise identical withdrawals have unique hashes
uint256 nonce;
// Block number when the Withdrawal was created
uint32 startBlock;
// Array of strategies that the Withdrawal contains
IStrategy[] strategies;
// Array containing the amount of shares in each Strategy in the `strategies` array
uint256[] shares;
}
struct QueuedWithdrawalParams {
// Array of strategies that the QueuedWithdrawal contains
IStrategy[] strategies;
// Array containing the amount of shares in each Strategy in the `strategies` array
uint256[] shares;
// The address of the withdrawer
address withdrawer;
}
// @notice Emitted when a new operator registers in EigenLayer and provides their OperatorDetails.
event OperatorRegistered(address indexed operator, OperatorDetails operatorDetails);
/// @notice Emitted when an operator updates their OperatorDetails to @param newOperatorDetails
event OperatorDetailsModified(address indexed operator, OperatorDetails newOperatorDetails);
/**
* @notice Emitted when @param operator indicates that they are updating their MetadataURI string
* @dev Note that these strings are *never stored in storage* and are instead purely emitted in events for off-chain
* indexing
*/
event OperatorMetadataURIUpdated(address indexed operator, string metadataURI);
/// @notice Emitted whenever an operator's shares are increased for a given strategy. Note that shares is the delta
/// in the operator's shares.
event OperatorSharesIncreased(address indexed operator, address staker, IStrategy strategy, uint256 shares);
/// @notice Emitted whenever an operator's shares are decreased for a given strategy. Note that shares is the delta
/// in the operator's shares.
event OperatorSharesDecreased(address indexed operator, address staker, IStrategy strategy, uint256 shares);
/// @notice Emitted when @param staker delegates to @param operator.
event StakerDelegated(address indexed staker, address indexed operator);
/// @notice Emitted when @param staker undelegates from @param operator.
event StakerUndelegated(address indexed staker, address indexed operator);
/// @notice Emitted when @param staker is undelegated via a call not originating from the staker themself
event StakerForceUndelegated(address indexed staker, address indexed operator);
/**
* @notice Emitted when a new withdrawal is queued.
* @param withdrawalRoot Is the hash of the `withdrawal`.
* @param withdrawal Is the withdrawal itself.
*/
event WithdrawalQueued(bytes32 withdrawalRoot, Withdrawal withdrawal);
/// @notice Emitted when a queued withdrawal is completed
event WithdrawalCompleted(bytes32 withdrawalRoot);
/// @notice Emitted when a queued withdrawal is *migrated* from the StrategyManager to the DelegationManager
event WithdrawalMigrated(bytes32 oldWithdrawalRoot, bytes32 newWithdrawalRoot);
/// @notice Emitted when the `minWithdrawalDelayBlocks` variable is modified from `previousValue` to `newValue`.
event MinWithdrawalDelayBlocksSet(uint256 previousValue, uint256 newValue);
/// @notice Emitted when the `strategyWithdrawalDelayBlocks` variable is modified from `previousValue` to
/// `newValue`.
event StrategyWithdrawalDelayBlocksSet(IStrategy strategy, uint256 previousValue, uint256 newValue);
/**
* @notice Registers the caller as an operator in EigenLayer.
* @param registeringOperatorDetails is the `OperatorDetails` for the operator.
* @param metadataURI is a URI for the operator's metadata, i.e. a link providing more details on the operator.
*
* @dev Once an operator is registered, they cannot 'deregister' as an operator, and they will forever be considered
* "delegated to themself".
* @dev This function will revert if the caller attempts to set their `earningsReceiver` to address(0).
* @dev Note that the `metadataURI` is *never stored * and is only emitted in the `OperatorMetadataURIUpdated` event
*/
function registerAsOperator(
OperatorDetails calldata registeringOperatorDetails,
string calldata metadataURI
)
external;
/**
* @notice Updates an operator's stored `OperatorDetails`.
* @param newOperatorDetails is the updated `OperatorDetails` for the operator, to replace their current
* OperatorDetails`.
*
* @dev The caller must have previously registered as an operator in EigenLayer.
* @dev This function will revert if the caller attempts to set their `earningsReceiver` to address(0).
*/
function modifyOperatorDetails(OperatorDetails calldata newOperatorDetails) external;
/**
* @notice Called by an operator to emit an `OperatorMetadataURIUpdated` event indicating the information has
* updated.
* @param metadataURI The URI for metadata associated with an operator
* @dev Note that the `metadataURI` is *never stored * and is only emitted in the `OperatorMetadataURIUpdated` event
*/
function updateOperatorMetadataURI(string calldata metadataURI) external;
/**
* @notice Caller delegates their stake to an operator.
* @param operator The account (`msg.sender`) is delegating its assets to for use in serving applications built on
* EigenLayer.
* @param approverSignatureAndExpiry Verifies the operator approves of this delegation
* @param approverSalt A unique single use value tied to an individual signature.
* @dev The approverSignatureAndExpiry is used in the event that:
* 1) the operator's `delegationApprover` address is set to a non-zero value.
* AND
* 2) neither the operator nor their `delegationApprover` is the `msg.sender`, since in the event that the
* operator
* or their delegationApprover is the `msg.sender`, then approval is assumed.
* @dev In the event that `approverSignatureAndExpiry` is not checked, its content is ignored entirely; it's
* recommended to use an empty input
* in this case to save on complexity + gas costs
*/
function delegateTo(
address operator,
SignatureWithExpiry memory approverSignatureAndExpiry,
bytes32 approverSalt
)
external;
/**
* @notice Caller delegates a staker's stake to an operator with valid signatures from both parties.
* @param staker The account delegating stake to an `operator` account
* @param operator The account (`staker`) is delegating its assets to for use in serving applications built on
* EigenLayer.
* @param stakerSignatureAndExpiry Signed data from the staker authorizing delegating stake to an operator
* @param approverSignatureAndExpiry is a parameter that will be used for verifying that the operator approves of
* this delegation action in the event that:
* @param approverSalt Is a salt used to help guarantee signature uniqueness. Each salt can only be used once by a
* given approver.
*
* @dev If `staker` is an EOA, then `stakerSignature` is verified to be a valid ECDSA stakerSignature from `staker`,
* indicating their intention for this action.
* @dev If `staker` is a contract, then `stakerSignature` will be checked according to EIP-1271.
* @dev the operator's `delegationApprover` address is set to a non-zero value.
* @dev neither the operator nor their `delegationApprover` is the `msg.sender`, since in the event that the
* operator or their delegationApprover
* is the `msg.sender`, then approval is assumed.
* @dev This function will revert if the current `block.timestamp` is equal to or exceeds the expiry
* @dev In the case that `approverSignatureAndExpiry` is not checked, its content is ignored entirely; it's
* recommended to use an empty input
* in this case to save on complexity + gas costs
*/
function delegateToBySignature(
address staker,
address operator,
SignatureWithExpiry memory stakerSignatureAndExpiry,
SignatureWithExpiry memory approverSignatureAndExpiry,
bytes32 approverSalt
)
external;
/**
* @notice Undelegates the staker from the operator who they are delegated to. Puts the staker into the
* "undelegation limbo" mode of the EigenPodManager
* and queues a withdrawal of all of the staker's shares in the StrategyManager (to the staker), if necessary.
* @param staker The account to be undelegated.
* @return withdrawalRoot The root of the newly queued withdrawal, if a withdrawal was queued. Otherwise just
* bytes32(0).
*
* @dev Reverts if the `staker` is also an operator, since operators are not allowed to undelegate from themselves.
* @dev Reverts if the caller is not the staker, nor the operator who the staker is delegated to, nor the operator's
* specified "delegationApprover"
* @dev Reverts if the `staker` is already undelegated.
*/
function undelegate(address staker) external returns (bytes32[] memory withdrawalRoot);
/**
* Allows a staker to withdraw some shares. Withdrawn shares/strategies are immediately removed
* from the staker. If the staker is delegated, withdrawn shares/strategies are also removed from
* their operator.
*
* All withdrawn shares/strategies are placed in a queue and can be fully withdrawn after a delay.
*/
function queueWithdrawals(QueuedWithdrawalParams[] calldata queuedWithdrawalParams)
external
returns (bytes32[] memory);
/**
* @notice Used to complete the specified `withdrawal`. The caller must match `withdrawal.withdrawer`
* @param withdrawal The Withdrawal to complete.
* @param tokens Array in which the i-th entry specifies the `token` input to the 'withdraw' function of the i-th
* Strategy in the `withdrawal.strategies` array.
* This input can be provided with zero length if `receiveAsTokens` is set to 'false' (since in that case, this
* input will be unused)
* @param middlewareTimesIndex is the index in the operator that the staker who triggered the withdrawal was
* delegated to's middleware times array
* @param receiveAsTokens If true, the shares specified in the withdrawal will be withdrawn from the specified
* strategies themselves
* and sent to the caller, through calls to `withdrawal.strategies[i].withdraw`. If false, then the shares in the
* specified strategies
* will simply be transferred to the caller directly.
* @dev middlewareTimesIndex should be calculated off chain before calling this function by finding the first index
* that satisfies `slasher.canWithdraw`
* @dev beaconChainETHStrategy shares are non-transferrable, so if `receiveAsTokens = false` and
* `withdrawal.withdrawer != withdrawal.staker`, note that
* any beaconChainETHStrategy shares in the `withdrawal` will be _returned to the staker_, rather than transferred
* to the withdrawer, unlike shares in
* any other strategies, which will be transferred to the withdrawer.
*/
function completeQueuedWithdrawal(
Withdrawal calldata withdrawal,
IERC20[] calldata tokens,
uint256 middlewareTimesIndex,
bool receiveAsTokens
)
external;
/**
* @notice Array-ified version of `completeQueuedWithdrawal`.
* Used to complete the specified `withdrawals`. The function caller must match `withdrawals[...].withdrawer`
* @param withdrawals The Withdrawals to complete.
* @param tokens Array of tokens for each Withdrawal. See `completeQueuedWithdrawal` for the usage of a single
* array.
* @param middlewareTimesIndexes One index to reference per Withdrawal. See `completeQueuedWithdrawal` for the usage
* of a single index.
* @param receiveAsTokens Whether or not to complete each withdrawal as tokens. See `completeQueuedWithdrawal` for
* the usage of a single boolean.
* @dev See `completeQueuedWithdrawal` for relevant dev tags
*/
function completeQueuedWithdrawals(
Withdrawal[] calldata withdrawals,
IERC20[][] calldata tokens,
uint256[] calldata middlewareTimesIndexes,
bool[] calldata receiveAsTokens
)
external;
/**
* @notice Increases a staker's delegated share balance in a strategy.
* @param staker The address to increase the delegated shares for their operator.
* @param strategy The strategy in which to increase the delegated shares.
* @param shares The number of shares to increase.
*
* @dev *If the staker is actively delegated*, then increases the `staker`'s delegated shares in `strategy` by
* `shares`. Otherwise does nothing.
* @dev Callable only by the StrategyManager or EigenPodManager.
*/
function increaseDelegatedShares(address staker, IStrategy strategy, uint256 shares) external;
/**
* @notice Decreases a staker's delegated share balance in a strategy.
* @param staker The address to increase the delegated shares for their operator.
* @param strategy The strategy in which to decrease the delegated shares.
* @param shares The number of shares to decrease.
*
* @dev *If the staker is actively delegated*, then decreases the `staker`'s delegated shares in `strategy` by
* `shares`. Otherwise does nothing.
* @dev Callable only by the StrategyManager or EigenPodManager.
*/
function decreaseDelegatedShares(address staker, IStrategy strategy, uint256 shares) external;
/**
* @notice returns the address of the operator that `staker` is delegated to.
* @notice Mapping: staker => operator whom the staker is currently delegated to.
* @dev Note that returning address(0) indicates that the staker is not actively delegated to any operator.
*/
function delegatedTo(address staker) external view returns (address);
/**
* @notice Returns the OperatorDetails struct associated with an `operator`.
*/
function operatorDetails(address operator) external view returns (OperatorDetails memory);
/*
* @notice Returns the earnings receiver address for an operator
*/
function earningsReceiver(address operator) external view returns (address);
/**
* @notice Returns the delegationApprover account for an operator
*/
function delegationApprover(address operator) external view returns (address);
/**
* @notice Returns the stakerOptOutWindowBlocks for an operator
*/
function stakerOptOutWindowBlocks(address operator) external view returns (uint256);
/**
* @notice Given array of strategies, returns array of shares for the operator
*/
function getOperatorShares(
address operator,
IStrategy[] memory strategies
)
external
view
returns (uint256[] memory);
/**
* @notice Given a list of strategies, return the minimum number of blocks that must pass to withdraw
* from all the inputted strategies. Return value is >= minWithdrawalDelayBlocks as this is the global min
* withdrawal delay.
* @param strategies The strategies to check withdrawal delays for
*/
function getWithdrawalDelay(IStrategy[] calldata strategies) external view returns (uint256);
/**
* @notice returns the total number of shares in `strategy` that are delegated to `operator`.
* @notice Mapping: operator => strategy => total number of shares in the strategy delegated to the operator.
* @dev By design, the following invariant should hold for each Strategy:
* (operator's shares in delegation manager) = sum (shares above zero of all stakers delegated to operator)
* = sum (delegateable shares of all stakers delegated to the operator)
*/
function operatorShares(address operator, IStrategy strategy) external view returns (uint256);
/**
* @notice Returns 'true' if `staker` *is* actively delegated, and 'false' otherwise.
*/
function isDelegated(address staker) external view returns (bool);
/**
* @notice Returns true is an operator has previously registered for delegation.
*/
function isOperator(address operator) external view returns (bool);
/// @notice Mapping: staker => number of signed delegation nonces (used in `delegateToBySignature`) from the staker
/// that the contract has already checked
function stakerNonce(address staker) external view returns (uint256);
/**
* @notice Mapping: delegationApprover => 32-byte salt => whether or not the salt has already been used by the
* delegationApprover.
* @dev Salts are used in the `delegateTo` and `delegateToBySignature` functions. Note that these functions only
* process the delegationApprover's
* signature + the provided salt if the operator being delegated to has specified a nonzero address as their
* `delegationApprover`.
*/
function delegationApproverSaltIsSpent(address _delegationApprover, bytes32 salt) external view returns (bool);
/**
* @notice Minimum delay enforced by this contract for completing queued withdrawals. Measured in blocks, and
* adjustable by this contract's owner,
* up to a maximum of `MAX_WITHDRAWAL_DELAY_BLOCKS`. Minimum value is 0 (i.e. no delay enforced).
* Note that strategies each have a separate withdrawal delay, which can be greater than this value. So the minimum
* number of blocks that must pass
* to withdraw a strategy is MAX(minWithdrawalDelayBlocks, strategyWithdrawalDelayBlocks[strategy])
*/
function minWithdrawalDelayBlocks() external view returns (uint256);
/**
* @notice Minimum delay enforced by this contract per Strategy for completing queued withdrawals. Measured in
* blocks, and adjustable by this contract's owner,
* up to a maximum of `MAX_WITHDRAWAL_DELAY_BLOCKS`. Minimum value is 0 (i.e. no delay enforced).
*/
function strategyWithdrawalDelayBlocks(IStrategy strategy) external view returns (uint256);
/**
* @notice Calculates the digestHash for a `staker` to sign to delegate to an `operator`
* @param staker The signing staker
* @param operator The operator who is being delegated to
* @param expiry The desired expiry time of the staker's signature
*/
function calculateCurrentStakerDelegationDigestHash(
address staker,
address operator,
uint256 expiry
)
external
view
returns (bytes32);
/**
* @notice Calculates the digest hash to be signed and used in the `delegateToBySignature` function
* @param staker The signing staker
* @param _stakerNonce The nonce of the staker. In practice we use the staker's current nonce, stored at
* `stakerNonce[staker]`
* @param operator The operator who is being delegated to
* @param expiry The desired expiry time of the staker's signature
*/
function calculateStakerDelegationDigestHash(
address staker,
uint256 _stakerNonce,
address operator,
uint256 expiry
)
external
view
returns (bytes32);
/**
* @notice Calculates the digest hash to be signed by the operator's delegationApprove and used in the `delegateTo`
* and `delegateToBySignature` functions.
* @param staker The account delegating their stake
* @param operator The account receiving delegated stake
* @param _delegationApprover the operator's `delegationApprover` who will be signing the delegationHash (in
* general)
* @param approverSalt A unique and single use value associated with the approver signature.
* @param expiry Time after which the approver's signature becomes invalid
*/
function calculateDelegationApprovalDigestHash(
address staker,
address operator,
address _delegationApprover,
bytes32 approverSalt,
uint256 expiry
)
external
view
returns (bytes32);
/// @notice The EIP-712 typehash for the contract's domain
function DOMAIN_TYPEHASH() external view returns (bytes32);
/// @notice The EIP-712 typehash for the StakerDelegation struct used by the contract
function STAKER_DELEGATION_TYPEHASH() external view returns (bytes32);
/// @notice The EIP-712 typehash for the DelegationApproval struct used by the contract
function DELEGATION_APPROVAL_TYPEHASH() external view returns (bytes32);
/**
* @notice Getter function for the current EIP-712 domain separator for this contract.
*
* @dev The domain separator will change in the event of a fork that changes the ChainID.
* @dev By introducing a domain separator the DApp developers are guaranteed that there can be no signature
* collision.
* for more detailed information please read EIP-712.
*/
function domainSeparator() external view returns (bytes32);
/// @notice Mapping: staker => cumulative number of queued withdrawals they have ever initiated.
/// @dev This only increments (doesn't decrement), and is used to help ensure that otherwise identical withdrawals
/// have unique hashes.
function cumulativeWithdrawalsQueued(address staker) external view returns (uint256);
/// @notice Returns the keccak256 hash of `withdrawal`.
function calculateWithdrawalRoot(Withdrawal memory withdrawal) external pure returns (bytes32);
function getDelegatableShares(address staker) external view returns (IStrategy[] memory, uint256[] memory);
function pendingWithdrawals(bytes32 withdrawalRoot) external view;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (interfaces/IERC20.sol)
pragma solidity ^0.8.0;
import "../token/ERC20/IERC20.sol";
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;
import "../../utils/external/BeaconChainProofs.sol";
import "./IEigenPodManager.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @title The implementation contract used for restaking beacon chain ETH on EigenLayer
* @author Layr Labs, Inc.
* @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
* @dev Note that all beacon chain balances are stored as gwei within the beacon chain datastructures. We choose
* to account balances in terms of gwei in the EigenPod contract and convert to wei when making calls to other
* contracts
*/
interface IEigenPod {
/**
*
* STRUCTS / ENUMS
*
*/
enum VALIDATOR_STATUS {
INACTIVE, // doesnt exist
ACTIVE, // staked on ethpos and withdrawal credentials are pointed to the EigenPod
WITHDRAWN // withdrawn from the Beacon Chain
}
struct ValidatorInfo {
// index of the validator in the beacon chain
uint64 validatorIndex;
// amount of beacon chain ETH restaked on EigenLayer in gwei
uint64 restakedBalanceGwei;
//timestamp of the validator's most recent balance update
uint64 lastCheckpointedAt;
// status of the validator
VALIDATOR_STATUS status;
}
struct Checkpoint {
bytes32 beaconBlockRoot;
uint24 proofsRemaining;
uint64 podBalanceGwei;
int128 balanceDeltasGwei;
}
/**
*
* EVENTS
*
*/
/// @notice Emitted when an ETH validator stakes via this eigenPod
event EigenPodStaked(bytes pubkey);
/// @notice Emitted when a pod owner updates the proof submitter address
event ProofSubmitterUpdated(address prevProofSubmitter, address newProofSubmitter);
/// @notice Emitted when an ETH validator's withdrawal credentials are successfully verified to be pointed to this
/// eigenPod
event ValidatorRestaked(uint40 validatorIndex);
/// @notice Emitted when an ETH validator's balance is proven to be updated. Here newValidatorBalanceGwei
// is the validator's balance that is credited on EigenLayer.
event ValidatorBalanceUpdated(uint40 validatorIndex, uint64 balanceTimestamp, uint64 newValidatorBalanceGwei);
/// @notice Emitted when restaked beacon chain ETH is withdrawn from the eigenPod.
event RestakedBeaconChainETHWithdrawn(address indexed recipient, uint256 amount);
/// @notice Emitted when ETH is received via the `receive` fallback
event NonBeaconChainETHReceived(uint256 amountReceived);
/// @notice Emitted when a checkpoint is created
event CheckpointCreated(uint64 indexed checkpointTimestamp, bytes32 indexed beaconBlockRoot);
/// @notice Emitted when a checkpoint is finalized
event CheckpointFinalized(uint64 indexed checkpointTimestamp, int256 totalShareDeltaWei);
/// @notice Emitted when a validator is proven for a given checkpoint
event ValidatorCheckpointed(uint64 indexed checkpointTimestamp, uint40 indexed validatorIndex);
/// @notice Emitted when a validaor is proven to have 0 balance at a given checkpoint
event ValidatorWithdrawn(uint64 indexed checkpointTimestamp, uint40 indexed validatorIndex);
/**
*
* EXTERNAL STATE-CHANGING METHODS
*
*/
/// @notice Used to initialize the pointers to contracts crucial to the pod's functionality, in beacon proxy
/// construction from EigenPodManager
function initialize(address owner) external;
/// @notice Called by EigenPodManager when the owner wants to create another ETH validator.
function stake(bytes calldata pubkey, bytes calldata signature, bytes32 depositDataRoot) external payable;
/**
* @notice Transfers `amountWei` in ether from this contract to the specified `recipient` address
* @notice Called by EigenPodManager to withdrawBeaconChainETH that has been added to the EigenPod's balance due to
* a withdrawal from the beacon chain.
* @dev The podOwner must have already proved sufficient withdrawals, so that this pod's
* `withdrawableRestakedExecutionLayerGwei` exceeds the
* `amountWei` input (when converted to GWEI).
* @dev Reverts if `amountWei` is not a whole Gwei amount
*/
function withdrawRestakedBeaconChainETH(address recipient, uint256 amount) external;
/**
* @dev Create a checkpoint used to prove this pod's active validator set. Checkpoints are completed
* by submitting one checkpoint proof per ACTIVE validator. During the checkpoint process, the total
* change in ACTIVE validator balance is tracked, and any validators with 0 balance are marked `WITHDRAWN`.
* @dev Once finalized, the pod owner is awarded shares corresponding to:
* - the total change in their ACTIVE validator balances
* - any ETH in the pod not already awarded shares
* @dev A checkpoint cannot be created if the pod already has an outstanding checkpoint. If
* this is the case, the pod owner MUST complete the existing checkpoint before starting a new one.
* @param revertIfNoBalance Forces a revert if the pod ETH balance is 0. This allows the pod owner
* to prevent accidentally starting a checkpoint that will not increase their shares
*/
function startCheckpoint(bool revertIfNoBalance) external;
/**
* @dev Progress the current checkpoint towards completion by submitting one or more validator
* checkpoint proofs. Anyone can call this method to submit proofs towards the current checkpoint.
* For each validator proven, the current checkpoint's `proofsRemaining` decreases.
* @dev If the checkpoint's `proofsRemaining` reaches 0, the checkpoint is finalized.
* (see `_updateCheckpoint` for more details)
* @dev This method can only be called when there is a currently-active checkpoint.
* @param balanceContainerProof proves the beacon's current balance container root against a checkpoint's
* `beaconBlockRoot`
* @param proofs Proofs for one or more validator current balances against the `balanceContainerRoot`
*/
function verifyCheckpointProofs(
BeaconChainProofs.BalanceContainerProof calldata balanceContainerProof,
BeaconChainProofs.BalanceProof[] calldata proofs
)
external;
/**
* @dev Verify one or more validators have their withdrawal credentials pointed at this EigenPod, and award
* shares based on their effective balance. Proven validators are marked `ACTIVE` within the EigenPod, and
* future checkpoint proofs will need to include them.
* @dev Withdrawal credential proofs MUST NOT be older than `currentCheckpointTimestamp`.
* @dev Validators proven via this method MUST NOT have an exit epoch set already.
* @param beaconTimestamp the beacon chain timestamp sent to the 4788 oracle contract. Corresponds
* to the parent beacon block root against which the proof is verified.
* @param stateRootProof proves a beacon state root against a beacon block root
* @param validatorIndices a list of validator indices being proven
* @param validatorFieldsProofs proofs of each validator's `validatorFields` against the beacon state root
* @param validatorFields the fields of the beacon chain "Validator" container. See consensus specs for
* details: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator
*/
function verifyWithdrawalCredentials(
uint64 beaconTimestamp,
BeaconChainProofs.StateRootProof calldata stateRootProof,
uint40[] calldata validatorIndices,
bytes[] calldata validatorFieldsProofs,
bytes32[][] calldata validatorFields
)
external;
/**
* @dev Prove that one of this pod's active validators was slashed on the beacon chain. A successful
* staleness proof allows the caller to start a checkpoint.
*
* @dev Note that in order to start a checkpoint, any existing checkpoint must already be completed!
* (See `_startCheckpoint` for details)
*
* @dev Note that this method allows anyone to start a checkpoint as soon as a slashing occurs on the beacon
* chain. This is intended to make it easier to external watchers to keep a pod's balance up to date.
*
* @dev Note too that beacon chain slashings are not instant. There is a delay between the initial slashing event
* and the validator's final exit back to the execution layer. During this time, the validator's balance may or
* may not drop further due to a correlation penalty. This method allows proof of a slashed validator
* to initiate a checkpoint for as long as the validator remains on the beacon chain. Once the validator
* has exited and been checkpointed at 0 balance, they are no longer "checkpoint-able" and cannot be proven
* "stale" via this method.
* See https://eth2book.info/capella/part3/transition/epoch/#slashings for more info.
*
* @param beaconTimestamp the beacon chain timestamp sent to the 4788 oracle contract. Corresponds
* to the parent beacon block root against which the proof is verified.
* @param stateRootProof proves a beacon state root against a beacon block root
* @param proof the fields of the beacon chain "Validator" container, along with a merkle proof against
* the beacon state root. See the consensus specs for more details:
* https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator
*
* @dev Staleness conditions:
* - Validator's last checkpoint is older than `beaconTimestamp`
* - Validator MUST be in `ACTIVE` status in the pod
* - Validator MUST be slashed on the beacon chain
*/
function verifyStaleBalance(
uint64 beaconTimestamp,
BeaconChainProofs.StateRootProof calldata stateRootProof,
BeaconChainProofs.ValidatorProof calldata proof
)
external;
/// @notice called by owner of a pod to remove any ERC20s deposited in the pod
function recoverTokens(IERC20[] memory tokenList, uint256[] memory amountsToWithdraw, address recipient) external;
/// @notice Allows the owner of a pod to update the proof submitter, a permissioned
/// address that can call `startCheckpoint` and `verifyWithdrawalCredentials`.
/// @dev Note that EITHER the podOwner OR proofSubmitter can access these methods,
/// so it's fine to set your proofSubmitter to 0 if you want the podOwner to be the
/// only address that can call these methods.
/// @param newProofSubmitter The new proof submitter address. If set to 0, only the
/// pod owner will be able to call `startCheckpoint` and `verifyWithdrawalCredentials`
function setProofSubmitter(address newProofSubmitter) external;
/**
*
* VIEW METHODS
*
*/
/// @notice An address with permissions to call `startCheckpoint` and `verifyWithdrawalCredentials`, set
/// by the podOwner. This role exists to allow a podOwner to designate a hot wallet that can call
/// these methods, allowing the podOwner to remain a cold wallet that is only used to manage funds.
/// @dev If this address is NOT set, only the podOwner can call `startCheckpoint` and `verifyWithdrawalCredentials`
function proofSubmitter() external view returns (address);
/// @notice the amount of execution layer ETH in this contract that is staked in EigenLayer (i.e. withdrawn from
/// beaconchain but not EigenLayer),
function withdrawableRestakedExecutionLayerGwei() external view returns (uint64);
/// @notice The single EigenPodManager for EigenLayer
function eigenPodManager() external view returns (IEigenPodManager);
/// @notice The owner of this EigenPod
function podOwner() external view returns (address);
/// @notice Returns the validatorInfo struct for the provided pubkeyHash
function validatorPubkeyHashToInfo(bytes32 validatorPubkeyHash) external view returns (ValidatorInfo memory);
/// @notice Returns the validatorInfo struct for the provided pubkey
function validatorPubkeyToInfo(bytes calldata validatorPubkey) external view returns (ValidatorInfo memory);
/// @notice This returns the status of a given validator
function validatorStatus(bytes32 pubkeyHash) external view returns (VALIDATOR_STATUS);
/// @notice This returns the status of a given validator pubkey
function validatorStatus(bytes calldata validatorPubkey) external view returns (VALIDATOR_STATUS);
/// @notice Number of validators with proven withdrawal credentials, who do not have proven full withdrawals
function activeValidatorCount() external view returns (uint256);
/// @notice The timestamp of the last checkpoint finalized
function lastCheckpointTimestamp() external view returns (uint64);
/// @notice The timestamp of the currently-active checkpoint. Will be 0 if there is not active checkpoint
function currentCheckpointTimestamp() external view returns (uint64);
/// @notice Returns the currently-active checkpoint
function currentCheckpoint() external view returns (Checkpoint memory);
/// @notice For each checkpoint, the total balance attributed to exited validators, in gwei
///
/// NOTE that the values added to this mapping are NOT guaranteed to capture the entirety of a validator's
/// exit - rather, they capture the total change in a validator's balance when a checkpoint shows their
/// balance change from nonzero to zero. While a change from nonzero to zero DOES guarantee that a validator
/// has been fully exited, it is possible that the magnitude of this change does not capture what is
/// typically thought of as a "full exit."
///
/// For example:
/// 1. Consider a validator was last checkpointed at 32 ETH before exiting. Once the exit has been processed,
/// it is expected that the validator's exited balance is calculated to be `32 ETH`.
/// 2. However, before `startCheckpoint` is called, a deposit is made to the validator for 1 ETH. The beacon
/// chain will automatically withdraw this ETH, but not until the withdrawal sweep passes over the validator
/// again. Until this occurs, the validator's current balance (used for checkpointing) is 1 ETH.
/// 3. If `startCheckpoint` is called at this point, the balance delta calculated for this validator will be
/// `-31 ETH`, and because the validator has a nonzero balance, it is not marked WITHDRAWN.
/// 4. After the exit is processed by the beacon chain, a subsequent `startCheckpoint` and checkpoint proof
/// will calculate a balance delta of `-1 ETH` and attribute a 1 ETH exit to the validator.
///
/// If this edge case impacts your usecase, it should be possible to mitigate this by monitoring for deposits
/// to your exited validators, and waiting to call `startCheckpoint` until those deposits have been automatically
/// exited.
///
/// Additional edge cases this mapping does not cover:
/// - If a validator is slashed, their balance exited will reflect their original balance rather than the slashed
/// amount
/// - The final partial withdrawal for an exited validator will be likely be included in this mapping.
/// i.e. if a validator was last checkpointed at 32.1 ETH before exiting, the next checkpoint will calculate their
/// "exited" amount to be 32.1 ETH rather than 32 ETH.
function checkpointBalanceExitedGwei(uint64) external view returns (uint64);
/// @notice Query the 4788 oracle to get the parent block root of the slot with the given `timestamp`
/// @param timestamp of the block for which the parent block root will be returned. MUST correspond
/// to an existing slot within the last 24 hours. If the slot at `timestamp` was skipped, this method
/// will revert.
function getParentBlockRoot(uint64 timestamp) external view returns (bytes32);
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;
import "@openzeppelin/contracts/proxy/beacon/IBeacon.sol";
import "./IStrategyManager.sol";
import "./IEigenPod.sol";
import "./IStrategy.sol";
/**
* @title Interface for factory that creates and manages solo staking pods that have their withdrawal credentials
* pointed to EigenLayer.
* @author Layr Labs, Inc.
* @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
*/
interface IEigenPodManager {
/// @notice Emitted to notify the deployment of an EigenPod
event PodDeployed(address indexed eigenPod, address indexed podOwner);
/// @notice Emitted to notify a deposit of beacon chain ETH recorded in the strategy manager
event BeaconChainETHDeposited(address indexed podOwner, uint256 amount);
/// @notice Emitted when the balance of an EigenPod is updated
event PodSharesUpdated(address indexed podOwner, int256 sharesDelta);
/// @notice Emitted when a withdrawal of beacon chain ETH is completed
event BeaconChainETHWithdrawalCompleted(
address indexed podOwner,
uint256 shares,
uint96 nonce,
address delegatedAddress,
address withdrawer,
bytes32 withdrawalRoot
);
/**
* @notice Creates an EigenPod for the sender.
* @dev Function will revert if the `msg.sender` already has an EigenPod.
* @dev Returns EigenPod address
*/
function createPod() external returns (address);
/**
* @notice Stakes for a new beacon chain validator on the sender's EigenPod.
* Also creates an EigenPod for the sender if they don't have one already.
* @param pubkey The 48 bytes public key of the beacon chain validator.
* @param signature The validator's signature of the deposit data.
* @param depositDataRoot The root/hash of the deposit data for the validator's deposit.
*/
function stake(bytes calldata pubkey, bytes calldata signature, bytes32 depositDataRoot) external payable;
/**
* @notice Changes the `podOwner`'s shares by `sharesDelta` and performs a call to the DelegationManager
* to ensure that delegated shares are also tracked correctly
* @param podOwner is the pod owner whose balance is being updated.
* @param sharesDelta is the change in podOwner's beaconChainETHStrategy shares
* @dev Callable only by the podOwner's EigenPod contract.
* @dev Reverts if `sharesDelta` is not a whole Gwei amount
*/
function recordBeaconChainETHBalanceUpdate(address podOwner, int256 sharesDelta) external;
/// @notice Returns the address of the `podOwner`'s EigenPod if it has been deployed.
function ownerToPod(address podOwner) external view returns (IEigenPod);
/// @notice Returns the address of the `podOwner`'s EigenPod (whether it is deployed yet or not).
function getPod(address podOwner) external view returns (IEigenPod);
/// @notice Beacon proxy to which the EigenPods point
function eigenPodBeacon() external view returns (IBeacon);
/// @notice EigenLayer's StrategyManager contract
function strategyManager() external view returns (IStrategyManager);
/// @notice Returns 'true' if the `podOwner` has created an EigenPod, and 'false' otherwise.
function hasPod(address podOwner) external view returns (bool);
/// @notice Returns the number of EigenPods that have been created
function numPods() external view returns (uint256);
/**
* @notice Mapping from Pod owner owner to the number of shares they have in the virtual beacon chain ETH strategy.
* @dev The share amount can become negative. This is necessary to accommodate the fact that a pod owner's virtual
* beacon chain ETH shares can
* decrease between the pod owner queuing and completing a withdrawal.
* When the pod owner's shares would otherwise increase, this "deficit" is decreased first _instead_.
* Likewise, when a withdrawal is completed, this "deficit" is decreased and the withdrawal amount is decreased; We
* can think of this
* as the withdrawal "paying off the deficit".
*/
function podOwnerShares(address podOwner) external view returns (int256);
/// @notice returns canonical, virtual beaconChainETH strategy
function beaconChainETHStrategy() external view returns (IStrategy);
/**
* @notice Used by the DelegationManager to remove a pod owner's shares while they're in the withdrawal queue.
* Simply decreases the `podOwner`'s shares by `shares`, down to a minimum of zero.
* @dev This function reverts if it would result in `podOwnerShares[podOwner]` being less than zero, i.e. it is
* forbidden for this function to
* result in the `podOwner` incurring a "share deficit". This behavior prevents a Staker from queuing a withdrawal
* which improperly removes excessive
* shares from the operator to whom the staker is delegated.
* @dev Reverts if `shares` is not a whole Gwei amount
*/
function removeShares(address podOwner, uint256 shares) external;
/**
* @notice Increases the `podOwner`'s shares by `shares`, paying off deficit if possible.
* Used by the DelegationManager to award a pod owner shares on exiting the withdrawal queue
* @dev Returns the number of shares added to `podOwnerShares[podOwner]` above zero, which will be less than the
* `shares` input
* in the event that the podOwner has an existing shares deficit (i.e. `podOwnerShares[podOwner]` starts below zero)
* @dev Reverts if `shares` is not a whole Gwei amount
*/
function addShares(address podOwner, uint256 shares) external returns (uint256);
/**
* @notice Used by the DelegationManager to complete a withdrawal, sending tokens to some destination address
* @dev Prioritizes decreasing the podOwner's share deficit, if they have one
* @dev Reverts if `shares` is not a whole Gwei amount
*/
function withdrawSharesAsTokens(address podOwner, address destination, uint256 shares) external;
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.21;
interface IEigenpieConfig {
// Errors
error ValueAlreadyInUse();
error AssetAlreadySupported();
error AssetNotSupported();
error CallerNotEigenpieConfigAdmin();
error CallerNotEigenpieConfigManager();
error CallerNotEigenpieConfigOracle();
error CallerNotEigenpieConfigOracleAdmin();
error CallerNotEigenpieConfigPriceProvider();
error CallerNotEigenpieConfigMinter();
error CallerNotEigenpieConfigBurner();
error CallerNotEigenpieConfigAllowedRole(string role);
error CallerNotEigenpieConfigAllowedBot();
// Events
event SetContract(bytes32 key, address indexed contractAddr);
event AddedNewSupportedAsset(address indexed asset, address indexed receipt, uint256 depositLimit);
event ReceiptTokenUpdated(address indexed asset, address indexed receipt);
event RemovedSupportedAsset(address indexed asset);
event AssetDepositLimitUpdate(address indexed asset, uint256 depositLimit);
event AssetStrategyUpdate(address indexed asset, address indexed strategy);
event AssetBoostUpdate(address indexed asset, uint256 newBoost);
event ReferralUpdate(address indexed me, address indexed myReferral);
// methods
function baseGasAmountSpent() external returns (uint256);
function assetStrategy(address asset) external view returns (address);
function boostByAsset(address) external view returns (uint256);
function mLRTReceiptByAsset(address) external view returns (address);
function isSupportedAsset(address asset) external view returns (bool);
function getContract(bytes32 contractId) external view returns (address);
function getSupportedAssetList() external view returns (address[] memory);
function depositLimitByAsset(address asset) external view returns (uint256);
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.21;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IMLRT is IERC20 {
function updateExchangeRateToLST(uint256 _newRate) external;
function exchangeRateToLST() external view returns (uint256);
function underlyingAsset() external view returns (address);
function mint(address to, uint256 amount) external;
function burnFrom(address account, uint256 amount) external;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)
pragma solidity 0.8.21;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IMintableERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
function mint(address, uint256) external;
function faucet(uint256) external;
function burnFrom(address account, uint256 amount) external;
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.21;
import "./eigenlayer/IStrategy.sol";
import "./ssvNetwork/ISSVNetworkCore.sol";
interface INodeDelegator {
// struct
struct DepositData {
bytes[] publicKeys;
bytes[] signatures;
bytes32[] depositDataRoots;
}
struct SSVPayload {
uint64[] operatorIds;
bytes[] sharesData;
uint256 amount;
ISSVNetworkCore.Cluster cluster;
}
// event
event AssetDepositIntoStrategy(address indexed asset, address indexed strategy, uint256 depositAmount);
event EigenPodCreated(address indexed createdEigenPod);
event RewardsForwarded(address indexed destinatino, uint256 amount);
event WithdrawalQueuedToEigenLayer(
bytes32[] withdrawalRoot,
IStrategy[] strategies,
address[] assets,
uint256[] withdrawalAmounts,
uint256 startBlock
);
event DelegationAddressUpdated(address delegate);
event GasSpent(address indexed spender, uint256 gasUsed);
event GasRefunded(address indexed receiver, uint256 gasRefund);
event NewProofSubmitter(address indexed oldProofSubmitter, address indexed _newProofSubmitter);
// errors
error TokenTransferFailed();
error StrategyIsNotSetForAsset();
error NoPubKeysProvided();
error EigenPodExisted();
error InvalidCall();
error InvalidCaller();
error AtLeastOneValidator();
error MaxValidatorsInput();
error PublicKeyNotMatch();
error SignaturesNotMatch();
error DelegateAddressAlreadySet();
error InvalidAmount();
// methods
function depositAssetIntoStrategy(address asset) external;
function maxApproveToEigenStrategyManager(address asset) external;
function getAssetBalances() external view returns (address[] memory, uint256[] memory);
function getAssetBalance(address asset) external view returns (uint256);
function getEthBalance() external view returns (uint256);
function createEigenPod() external;
function queueWithdrawalToEigenLayer(address[] memory assets, uint256[] memory amount) external;
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.21;
import "./ISSVNetworkCore.sol";
interface ISSVClusters is ISSVNetworkCore {
/// @notice Registers a new validator on the SSV Network
/// @param publicKey The public key of the new validator
/// @param operatorIds Array of IDs of operators managing this validator
/// @param sharesData Encrypted shares related to the new validator
/// @param amount Amount of SSV tokens to be deposited
/// @param cluster Cluster to be used with the new validator
function registerValidator(
bytes calldata publicKey,
uint64[] memory operatorIds,
bytes calldata sharesData,
uint256 amount,
Cluster memory cluster
)
external;
/// @notice Registers new validators on the SSV Network
/// @param publicKeys The public keys of the new validators
/// @param operatorIds Array of IDs of operators managing this validator
/// @param sharesData Encrypted shares related to the new validators
/// @param amount Amount of SSV tokens to be deposited
/// @param cluster Cluster to be used with the new validator
function bulkRegisterValidator(
bytes[] calldata publicKeys,
uint64[] memory operatorIds,
bytes[] calldata sharesData,
uint256 amount,
Cluster memory cluster
)
external;
/// @notice Removes an existing validator from the SSV Network
/// @param publicKey The public key of the validator to be removed
/// @param operatorIds Array of IDs of operators managing the validator
/// @param cluster Cluster associated with the validator
function removeValidator(bytes calldata publicKey, uint64[] memory operatorIds, Cluster memory cluster) external;
/// @notice Bulk removes a set of existing validators in the same cluster from the SSV Network
/// @notice Reverts if publicKeys contains duplicates or non-existent validators
/// @param publicKeys The public keys of the validators to be removed
/// @param operatorIds Array of IDs of operators managing the validator
/// @param cluster Cluster associated with the validator
function bulkRemoveValidator(
bytes[] calldata publicKeys,
uint64[] memory operatorIds,
Cluster memory cluster
)
external;
/**
*
*/
/* Cluster External Functions */
/**
*
*/
/// @notice Liquidates a cluster
/// @param owner The owner of the cluster
/// @param operatorIds Array of IDs of operators managing the cluster
/// @param cluster Cluster to be liquidated
function liquidate(address owner, uint64[] memory operatorIds, Cluster memory cluster) external;
/// @notice Reactivates a cluster
/// @param operatorIds Array of IDs of operators managing the cluster
/// @param amount Amount of SSV tokens to be deposited for reactivation
/// @param cluster Cluster to be reactivated
function reactivate(uint64[] memory operatorIds, uint256 amount, Cluster memory cluster) external;
/**
*
*/
/* Balance External Functions */
/**
*
*/
/// @notice Deposits tokens into a cluster
/// @param owner The owner of the cluster
/// @param operatorIds Array of IDs of operators managing the cluster
/// @param amount Amount of SSV tokens to be deposited
/// @param cluster Cluster where the deposit will be made
function deposit(address owner, uint64[] memory operatorIds, uint256 amount, Cluster memory cluster) external;
/// @notice Withdraws tokens from a cluster
/// @param operatorIds Array of IDs of operators managing the cluster
/// @param tokenAmount Amount of SSV tokens to be withdrawn
/// @param cluster Cluster where the withdrawal will be made
function withdraw(uint64[] memory operatorIds, uint256 tokenAmount, Cluster memory cluster) external;
/// @notice Fires the exit event for a validator
/// @param publicKey The public key of the validator to be exited
/// @param operatorIds Array of IDs of operators managing the validator
function exitValidator(bytes calldata publicKey, uint64[] calldata operatorIds) external;
/// @notice Fires the exit event for a set of validators
/// @param publicKeys The public keys of the validators to be exited
/// @param operatorIds Array of IDs of operators managing the validators
function bulkExitValidator(bytes[] calldata publicKeys, uint64[] calldata operatorIds) external;
/**
* @dev Emitted when the validator has been added.
* @param publicKey The public key of a validator.
* @param operatorIds The operator ids list.
* @param shares snappy compressed shares(a set of encrypted and public shares).
* @param cluster All the cluster data.
*/
event ValidatorAdded(address indexed owner, uint64[] operatorIds, bytes publicKey, bytes shares, Cluster cluster);
/**
* @dev Emitted when the validator is removed.
* @param publicKey The public key of a validator.
* @param operatorIds The operator ids list.
* @param cluster All the cluster data.
*/
event ValidatorRemoved(address indexed owner, uint64[] operatorIds, bytes publicKey, Cluster cluster);
/**
* @dev Emitted when a cluster is liquidated.
* @param owner The owner of the liquidated cluster.
* @param operatorIds The operator IDs managing the cluster.
* @param cluster The liquidated cluster data.
*/
event ClusterLiquidated(address indexed owner, uint64[] operatorIds, Cluster cluster);
/**
* @dev Emitted when a cluster is reactivated.
* @param owner The owner of the reactivated cluster.
* @param operatorIds The operator IDs managing the cluster.
* @param cluster The reactivated cluster data.
*/
event ClusterReactivated(address indexed owner, uint64[] operatorIds, Cluster cluster);
/**
* @dev Emitted when tokens are withdrawn from a cluster.
* @param owner The owner of the cluster.
* @param operatorIds The operator IDs managing the cluster.
* @param value The amount of tokens withdrawn.
* @param cluster The cluster from which tokens were withdrawn.
*/
event ClusterWithdrawn(address indexed owner, uint64[] operatorIds, uint256 value, Cluster cluster);
/**
* @dev Emitted when tokens are deposited into a cluster.
* @param owner The owner of the cluster.
* @param operatorIds The operator IDs managing the cluster.
* @param value The amount of SSV tokens deposited.
* @param cluster The cluster into which SSV tokens were deposited.
*/
event ClusterDeposited(address indexed owner, uint64[] operatorIds, uint256 value, Cluster cluster);
/**
* @dev Emitted when a validator begins the exit process.
* @param owner The owner of the exiting validator.
* @param operatorIds The operator IDs managing the validator.
* @param publicKey The public key of the exiting validator.
*/
event ValidatorExited(address indexed owner, uint64[] operatorIds, bytes publicKey);
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.21;
interface ISSVNetwork {
function setFeeRecipientAddress(address feeRecipientAddress) external;
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.21;
interface ISSVNetworkCore {
/**
*
*/
/* Structs */
/**
*
*/
/// @notice Represents a snapshot of an operator's or a DAO's state at a certain block
struct Snapshot {
/// @dev The block number when the snapshot was taken
uint32 block;
/// @dev The last index calculated by the formula index += (currentBlock - block) * fee
uint64 index;
/// @dev Total accumulated earnings calculated by the formula accumulated + lastIndex * validatorCount
uint64 balance;
}
/// @notice Represents an SSV operator
struct Operator {
/// @dev The number of validators associated with this operator
uint32 validatorCount;
/// @dev The fee charged by the operator, set to zero for private operators and cannot be increased once set
uint64 fee;
/// @dev The address of the operator's owner
address owner;
/// @dev Whitelisted flag for this operator
bool whitelisted;
/// @dev The state snapshot of the operator
Snapshot snapshot;
}
/// @notice Represents a request to change an operator's fee
struct OperatorFeeChangeRequest {
/// @dev The new fee proposed by the operator
uint64 fee;
/// @dev The time when the approval period for the fee change begins
uint64 approvalBeginTime;
/// @dev The time when the approval period for the fee change ends
uint64 approvalEndTime;
}
/// @notice Represents a cluster of validators
struct Cluster {
/// @dev The number of validators in the cluster
uint32 validatorCount;
/// @dev The index of network fees related to this cluster
uint64 networkFeeIndex;
/// @dev The last index calculated for the cluster
uint64 index;
/// @dev Flag indicating whether the cluster is active
bool active;
/// @dev The balance of the cluster
uint256 balance;
}
/**
*
*/
/* Errors */
/**
*
*/
error CallerNotOwner(); // 0x5cd83192
error CallerNotWhitelisted(); // 0x8c6e5d71
error FeeTooLow(); // 0x732f9413
error FeeExceedsIncreaseLimit(); // 0x958065d9
error NoFeeDeclared(); // 0x1d226c30
error ApprovalNotWithinTimeframe(); // 0x97e4b518
error OperatorDoesNotExist(); // 0x961e3e8c
error InsufficientBalance(); // 0xf4d678b8
error ValidatorDoesNotExist(); // 0xe51315d2
error ClusterNotLiquidatable(); // 0x60300a8d
error InvalidPublicKeyLength(); // 0x637297a4
error InvalidOperatorIdsLength(); // 0x38186224
error ClusterAlreadyEnabled(); // 0x3babafd2
error ClusterIsLiquidated(); // 0x95a0cf33
error ClusterDoesNotExists(); // 0x185e2b16
error IncorrectClusterState(); // 0x12e04c87
error UnsortedOperatorsList(); // 0xdd020e25
error NewBlockPeriodIsBelowMinimum(); // 0x6e6c9cac
error ExceedValidatorLimit(); // 0x6df5ab76
error TokenTransferFailed(); // 0x045c4b02
error SameFeeChangeNotAllowed(); // 0xc81272f8
error FeeIncreaseNotAllowed(); // 0x410a2b6c
error NotAuthorized(); // 0xea8e4eb5
error OperatorsListNotUnique(); // 0xa5a1ff5d
error OperatorAlreadyExists(); // 0x289c9494
error TargetModuleDoesNotExist(); // 0x8f9195fb
error MaxValueExceeded(); // 0x91aa3017
error FeeTooHigh(); // 0xcd4e6167
error PublicKeysSharesLengthMismatch(); // 0x9ad467b8
error IncorrectValidatorStateWithData(bytes publicKey); // 0x89307938
error ValidatorAlreadyExistsWithData(bytes publicKey); // 0x388e7999
// legacy errors
error ValidatorAlreadyExists(); // 0x8d09a73e
error IncorrectValidatorState(); // 0x2feda3c1
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;
/**
* @title The interface for common signature utilities.
* @author Layr Labs, Inc.
* @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
*/
interface ISignatureUtils {
// @notice Struct that bundles together a signature and an expiration time for the signature. Used primarily for
// stack management.
struct SignatureWithExpiry {
// the signature itself, formatted as a single bytes object
bytes signature;
// the expiration timestamp (UTC) of the signature
uint256 expiry;
}
// @notice Struct that bundles together a signature, a salt for uniqueness, and an expiration time for the
// signature. Used primarily for stack management.
struct SignatureWithSaltAndExpiry {
// the signature itself, formatted as a single bytes object
bytes signature;
// the salt used to generate the signature
bytes32 salt;
// the expiration timestamp (UTC) of the signature
uint256 expiry;
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @title Minimal interface for an `Strategy` contract.
* @author Layr Labs, Inc.
* @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
* @notice Custom `Strategy` implementations may expand extensively on this interface.
*/
interface IStrategy {
/**
* @notice Used to deposit tokens into this Strategy
* @param token is the ERC20 token being deposited
* @param amount is the amount of token being deposited
* @dev This function is only callable by the strategyManager contract. It is invoked inside of the
* strategyManager's
* `depositIntoStrategy` function, and individual share balances are recorded in the strategyManager as well.
* @return newShares is the number of new shares issued at the current exchange ratio.
*/
function deposit(IERC20 token, uint256 amount) external returns (uint256);
/**
* @notice Used to withdraw tokens from this Strategy, to the `recipient`'s address
* @param recipient is the address to receive the withdrawn funds
* @param token is the ERC20 token being transferred out
* @param amountShares is the amount of shares being withdrawn
* @dev This function is only callable by the strategyManager contract. It is invoked inside of the
* strategyManager's
* other functions, and individual share balances are recorded in the strategyManager as well.
*/
function withdraw(address recipient, IERC20 token, uint256 amountShares) external;
/**
* @notice Used to convert a number of shares to the equivalent amount of underlying tokens for this strategy.
* @notice In contrast to `sharesToUnderlyingView`, this function **may** make state modifications
* @param amountShares is the amount of shares to calculate its conversion into the underlying token
* @return The amount of underlying tokens corresponding to the input `amountShares`
* @dev Implementation for these functions in particular may vary significantly for different strategies
*/
function sharesToUnderlying(uint256 amountShares) external returns (uint256);
/**
* @notice Used to convert an amount of underlying tokens to the equivalent amount of shares in this strategy.
* @notice In contrast to `underlyingToSharesView`, this function **may** make state modifications
* @param amountUnderlying is the amount of `underlyingToken` to calculate its conversion into strategy shares
* @return The amount of underlying tokens corresponding to the input `amountShares`
* @dev Implementation for these functions in particular may vary significantly for different strategies
*/
function underlyingToShares(uint256 amountUnderlying) external returns (uint256);
/**
* @notice convenience function for fetching the current underlying value of all of the `user`'s shares in
* this strategy. In contrast to `userUnderlyingView`, this function **may** make state modifications
*/
function userUnderlying(address user) external returns (uint256);
/**
* @notice convenience function for fetching the current total shares of `user` in this strategy, by
* querying the `strategyManager` contract
*/
function shares(address user) external view returns (uint256);
/**
* @notice Used to convert a number of shares to the equivalent amount of underlying tokens for this strategy.
* @notice In contrast to `sharesToUnderlying`, this function guarantees no state modifications
* @param amountShares is the amount of shares to calculate its conversion into the underlying token
* @return The amount of shares corresponding to the input `amountUnderlying`
* @dev Implementation for these functions in particular may vary significantly for different strategies
*/
function sharesToUnderlyingView(uint256 amountShares) external view returns (uint256);
/**
* @notice Used to convert an amount of underlying tokens to the equivalent amount of shares in this strategy.
* @notice In contrast to `underlyingToShares`, this function guarantees no state modifications
* @param amountUnderlying is the amount of `underlyingToken` to calculate its conversion into strategy shares
* @return The amount of shares corresponding to the input `amountUnderlying`
* @dev Implementation for these functions in particular may vary significantly for different strategies
*/
function underlyingToSharesView(uint256 amountUnderlying) external view returns (uint256);
/**
* @notice convenience function for fetching the current underlying value of all of the `user`'s shares in
* this strategy. In contrast to `userUnderlying`, this function guarantees no state modifications
*/
function userUnderlyingView(address user) external view returns (uint256);
/// @notice The underlying token for shares in this Strategy
function underlyingToken() external view returns (IERC20);
/// @notice The total number of extant shares in this Strategy
function totalShares() external view returns (uint256);
/// @notice Returns either a brief string explaining the strategy's goal & purpose, or a link to metadata that
/// explains in more detail.
function explanation() external view returns (string memory);
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;
import "./IStrategy.sol";
import "./IDelegationManager.sol";
import "./IEigenPodManager.sol";
/**
* @title Interface for the primary entrypoint for funds into EigenLayer.
* @author Layr Labs, Inc.
* @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
* @notice See the `StrategyManager` contract itself for implementation details.
*/
interface IStrategyManager {
/**
* @notice Emitted when a new deposit occurs on behalf of `staker`.
* @param staker Is the staker who is depositing funds into EigenLayer.
* @param strategy Is the strategy that `staker` has deposited into.
* @param token Is the token that `staker` deposited.
* @param shares Is the number of new shares `staker` has been granted in `strategy`.
*/
event Deposit(address staker, IERC20 token, IStrategy strategy, uint256 shares);
/// @notice Emitted when `thirdPartyTransfersForbidden` is updated for a strategy and value by the owner
event UpdatedThirdPartyTransfersForbidden(IStrategy strategy, bool value);
/// @notice Emitted when the `strategyWhitelister` is changed
event StrategyWhitelisterChanged(address previousAddress, address newAddress);
/// @notice Emitted when a strategy is added to the approved list of strategies for deposit
event StrategyAddedToDepositWhitelist(IStrategy strategy);
/// @notice Emitted when a strategy is removed from the approved list of strategies for deposit
event StrategyRemovedFromDepositWhitelist(IStrategy strategy);
/**
* @notice Deposits `amount` of `token` into the specified `strategy`, with the resultant shares credited to
* `msg.sender`
* @param strategy is the specified strategy where deposit is to be made,
* @param token is the denomination in which the deposit is to be made,
* @param amount is the amount of token to be deposited in the strategy by the staker
* @return shares The amount of new shares in the `strategy` created as part of the action.
* @dev The `msg.sender` must have previously approved this contract to transfer at least `amount` of `token` on
* their behalf.
* @dev Cannot be called by an address that is 'frozen' (this function will revert if the `msg.sender` is frozen).
*
* WARNING: Depositing tokens that allow reentrancy (eg. ERC-777) into a strategy is not recommended. This can lead
* to attack vectors
* where the token balance and corresponding strategy shares are not in sync upon reentrancy.
*/
function depositIntoStrategy(IStrategy strategy, IERC20 token, uint256 amount) external returns (uint256 shares);
/**
* @notice Used for depositing an asset into the specified strategy with the resultant shares credited to `staker`,
* who must sign off on the action.
* Note that the assets are transferred out/from the `msg.sender`, not from the `staker`; this function is
* explicitly designed
* purely to help one address deposit 'for' another.
* @param strategy is the specified strategy where deposit is to be made,
* @param token is the denomination in which the deposit is to be made,
* @param amount is the amount of token to be deposited in the strategy by the staker
* @param staker the staker that the deposited assets will be credited to
* @param expiry the timestamp at which the signature expires
* @param signature is a valid signature from the `staker`. either an ECDSA signature if the `staker` is an EOA, or
* data to forward
* following EIP-1271 if the `staker` is a contract
* @return shares The amount of new shares in the `strategy` created as part of the action.
* @dev The `msg.sender` must have previously approved this contract to transfer at least `amount` of `token` on
* their behalf.
* @dev A signature is required for this function to eliminate the possibility of griefing attacks, specifically
* those
* targeting stakers who may be attempting to undelegate.
* @dev Cannot be called if thirdPartyTransfersForbidden is set to true for this strategy
*
* WARNING: Depositing tokens that allow reentrancy (eg. ERC-777) into a strategy is not recommended. This can
* lead to attack vectors
* where the token balance and corresponding strategy shares are not in sync upon reentrancy
*/
function depositIntoStrategyWithSignature(
IStrategy strategy,
IERC20 token,
uint256 amount,
address staker,
uint256 expiry,
bytes memory signature
)
external
returns (uint256 shares);
/// @notice Used by the DelegationManager to remove a Staker's shares from a particular strategy when entering the
/// withdrawal queue
function removeShares(address staker, IStrategy strategy, uint256 shares) external;
/// @notice Used by the DelegationManager to award a Staker some shares that have passed through the withdrawal
/// queue
function addShares(address staker, IERC20 token, IStrategy strategy, uint256 shares) external;
/// @notice Used by the DelegationManager to convert withdrawn shares to tokens and send them to a recipient
function withdrawSharesAsTokens(address recipient, IStrategy strategy, uint256 shares, IERC20 token) external;
/// @notice Returns the current shares of `user` in `strategy`
function stakerStrategyShares(address user, IStrategy strategy) external view returns (uint256 shares);
/**
* @notice Get all details on the staker's deposits and corresponding shares
* @return (staker's strategies, shares in these strategies)
*/
function getDeposits(address staker) external view returns (IStrategy[] memory, uint256[] memory);
/// @notice Simple getter function that returns `stakerStrategyList[staker].length`.
function stakerStrategyListLength(address staker) external view returns (uint256);
/**
* @notice Owner-only function that adds the provided Strategies to the 'whitelist' of strategies that stakers can
* deposit into
* @param strategiesToWhitelist Strategies that will be added to the `strategyIsWhitelistedForDeposit` mapping (if
* they aren't in it already)
* @param thirdPartyTransfersForbiddenValues bool values to set `thirdPartyTransfersForbidden` to for each strategy
*/
function addStrategiesToDepositWhitelist(
IStrategy[] calldata strategiesToWhitelist,
bool[] calldata thirdPartyTransfersForbiddenValues
)
external;
/**
* @notice Owner-only function that removes the provided Strategies from the 'whitelist' of strategies that stakers
* can deposit into
* @param strategiesToRemoveFromWhitelist Strategies that will be removed to the `strategyIsWhitelistedForDeposit`
* mapping (if they are in it)
*/
function removeStrategiesFromDepositWhitelist(IStrategy[] calldata strategiesToRemoveFromWhitelist) external;
/// @notice Returns the single, central Delegation contract of EigenLayer
function delegation() external view returns (IDelegationManager);
/// @notice Returns the EigenPodManager contract of EigenLayer
function eigenPodManager() external view returns (IEigenPodManager);
/// @notice Returns the address of the `strategyWhitelister`
function strategyWhitelister() external view returns (address);
/**
* @notice Returns bool for whether or not `strategy` enables credit transfers. i.e enabling
* depositIntoStrategyWithSignature calls or queueing withdrawals to a different address than the staker.
*/
function thirdPartyTransfersForbidden(IStrategy strategy) external view returns (bool);
// LIMITED BACKWARDS-COMPATIBILITY FOR DEPRECATED FUNCTIONALITY
// packed struct for queued withdrawals; helps deal with stack-too-deep errors
struct DeprecatedStruct_WithdrawerAndNonce {
address withdrawer;
uint96 nonce;
}
/**
* Struct type used to specify an existing queued withdrawal. Rather than storing the entire struct, only a hash is
* stored.
* In functions that operate on existing queued withdrawals -- e.g. `startQueuedWithdrawalWaitingPeriod` or
* `completeQueuedWithdrawal`,
* the data is resubmitted and the hash of the submitted data is computed by `calculateWithdrawalRoot` and checked
* against the
* stored hash in order to confirm the integrity of the submitted data.
*/
struct DeprecatedStruct_QueuedWithdrawal {
IStrategy[] strategies;
uint256[] shares;
address staker;
DeprecatedStruct_WithdrawerAndNonce withdrawerAndNonce;
uint32 withdrawalStartBlock;
address delegatedAddress;
}
function migrateQueuedWithdrawal(DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal)
external
returns (bool, bytes32);
function calculateWithdrawalRoot(DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal)
external
pure
returns (bytes32);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.2;
import "../../utils/AddressUpgradeable.sol";
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
* @custom:oz-retyped-from bool
*/
uint8 private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint8 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
* constructor.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
bool isTopLevelCall = !_initializing;
require(
(isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
"Initializable: contract is already initialized"
);
_initialized = 1;
if (isTopLevelCall) {
_initializing = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: setting the version to 255 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint8 version) {
require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
_initialized = version;
_initializing = true;
_;
_initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
require(_initializing, "Initializable: contract is not initializing");
_;
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
require(!_initializing, "Initializable: contract is initializing");
if (_initialized != type(uint8).max) {
_initialized = type(uint8).max;
emit Initialized(type(uint8).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint8) {
return _initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _initializing;
}
}
// SPDX-License-Identifier: MIT
// Adapted from OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/MerkleProof.sol)
pragma solidity ^0.8.0;
/**
* @dev These functions deal with verification of Merkle Tree proofs.
*
* The tree and the proofs can be generated using our
* https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
* You will find a quickstart guide in the readme.
*
* WARNING: You should avoid using leaf values that are 64 bytes long prior to
* hashing, or use a hash function other than keccak256 for hashing leaves.
* This is because the concatenation of a sorted pair of internal nodes in
* the merkle tree could be reinterpreted as a leaf value.
* OpenZeppelin's JavaScript library generates merkle trees that are safe
* against this attack out of the box.
*/
library Merkle {
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. The tree is built assuming `leaf` is
* the 0 indexed `index`'th leaf from the bottom left of the tree.
*
* Note this is for a Merkle tree using the keccak/sha3 hash function
*/
function verifyInclusionKeccak(
bytes memory proof,
bytes32 root,
bytes32 leaf,
uint256 index
)
internal
pure
returns (bool)
{
return processInclusionProofKeccak(proof, leaf, index) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. The tree is built assuming `leaf` is
* the 0 indexed `index`'th leaf from the bottom left of the tree.
* @dev If the proof length is 0 then the leaf hash is returned.
*
* _Available since v4.4._
*
* Note this is for a Merkle tree using the keccak/sha3 hash function
*/
function processInclusionProofKeccak(
bytes memory proof,
bytes32 leaf,
uint256 index
)
internal
pure
returns (bytes32)
{
require(proof.length % 32 == 0, "Merkle.processInclusionProofKeccak: proof length should be a multiple of 32");
bytes32 computedHash = leaf;
for (uint256 i = 32; i <= proof.length; i += 32) {
if (index % 2 == 0) {
// if ith bit of index is 0, then computedHash is a left sibling
assembly {
mstore(0x00, computedHash)
mstore(0x20, mload(add(proof, i)))
computedHash := keccak256(0x00, 0x40)
index := div(index, 2)
}
} else {
// if ith bit of index is 1, then computedHash is a right sibling
assembly {
mstore(0x00, mload(add(proof, i)))
mstore(0x20, computedHash)
computedHash := keccak256(0x00, 0x40)
index := div(index, 2)
}
}
}
return computedHash;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. The tree is built assuming `leaf` is
* the 0 indexed `index`'th leaf from the bottom left of the tree.
*
* Note this is for a Merkle tree using the sha256 hash function
*/
function verifyInclusionSha256(
bytes memory proof,
bytes32 root,
bytes32 leaf,
uint256 index
)
internal
view
returns (bool)
{
return processInclusionProofSha256(proof, leaf, index) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. The tree is built assuming `leaf` is
* the 0 indexed `index`'th leaf from the bottom left of the tree.
*
* _Available since v4.4._
*
* Note this is for a Merkle tree using the sha256 hash function
*/
function processInclusionProofSha256(
bytes memory proof,
bytes32 leaf,
uint256 index
)
internal
view
returns (bytes32)
{
require(
proof.length != 0 && proof.length % 32 == 0,
"Merkle.processInclusionProofSha256: proof length should be a non-zero multiple of 32"
);
bytes32[1] memory computedHash = [leaf];
for (uint256 i = 32; i <= proof.length; i += 32) {
if (index % 2 == 0) {
// if ith bit of index is 0, then computedHash is a left sibling
assembly {
mstore(0x00, mload(computedHash))
mstore(0x20, mload(add(proof, i)))
if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) { revert(0, 0) }
index := div(index, 2)
}
} else {
// if ith bit of index is 1, then computedHash is a right sibling
assembly {
mstore(0x00, mload(add(proof, i)))
mstore(0x20, mload(computedHash))
if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) { revert(0, 0) }
index := div(index, 2)
}
}
}
return computedHash[0];
}
/**
* @notice this function returns the merkle root of a tree created from a set of leaves using sha256 as its hash
* function
* @param leaves the leaves of the merkle tree
* @return The computed Merkle root of the tree.
* @dev A pre-condition to this function is that leaves.length is a power of two. If not, the function will
* merkleize the inputs incorrectly.
*/
function merkleizeSha256(bytes32[] memory leaves) internal pure returns (bytes32) {
//there are half as many nodes in the layer above the leaves
uint256 numNodesInLayer = leaves.length / 2;
//create a layer to store the internal nodes
bytes32[] memory layer = new bytes32[](numNodesInLayer);
//fill the layer with the pairwise hashes of the leaves
for (uint256 i = 0; i < numNodesInLayer; i++) {
layer[i] = sha256(abi.encodePacked(leaves[2 * i], leaves[2 * i + 1]));
}
//the next layer above has half as many nodes
numNodesInLayer /= 2;
//while we haven't computed the root
while (numNodesInLayer != 0) {
//overwrite the first numNodesInLayer nodes in layer with the pairwise hashes of their children
for (uint256 i = 0; i < numNodesInLayer; i++) {
layer[i] = sha256(abi.encodePacked(layer[2 * i], layer[2 * i + 1]));
}
//the next layer above has half as many nodes
numNodesInLayer /= 2;
}
//the first node in the layer is the root
return layer[0];
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.21;
import { UtilLib } from "./utils/UtilLib.sol";
import { BeaconChainProofs } from "./utils/external/BeaconChainProofs.sol";
import { TransferHelper } from "./utils/TransferHelper.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { EigenpieConstants } from "./utils/EigenpieConstants.sol";
import { EigenpieConfigRoleChecker, IEigenpieConfig } from "./utils/EigenpieConfigRoleChecker.sol";
import { INodeDelegator } from "./interfaces/INodeDelegator.sol";
import { IStrategy } from "./interfaces/eigenlayer/IStrategy.sol";
import { IDelegationManager } from "./interfaces/eigenlayer/IDelegationManager.sol";
import { IStrategyManager } from "./interfaces/eigenlayer/IStrategyManager.sol";
import { ISignatureUtils } from "./interfaces/eigenlayer/ISignatureUtils.sol";
import { IEigenPodManager, IEigenPod } from "./interfaces/eigenlayer/IEigenPodManager.sol";
import { IBeaconDepositContract } from "./interfaces/IBeaconDepositContract.sol";
import { ISSVClusters } from "./interfaces/ssvNetwork/ISSVClusters.sol";
import { ISSVNetwork } from "./interfaces/ssvNetwork/ISSVNetwork.sol";
import { ISSVNetworkCore } from "./interfaces/ssvNetwork/ISSVNetworkCore.sol";
import { ValidatorLib } from "./libraries/ValidatorLib.sol";
import { AssetManagementLib } from "./libraries/AssetManagementLib.sol";
import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
/// @title NodeDelegator Contract
/// @notice The contract that handles the depositing of assets into strategies
contract NodeDelegator is INodeDelegator, EigenpieConfigRoleChecker, PausableUpgradeable, ReentrancyGuardUpgradeable {
using SafeERC20 for IERC20;
IEigenPod public eigenPod;
/// @dev Tracks the balance staked to validators and has yet to have the credentials verified with EigenLayer.
/// call verifyWithdrawalCredentials to verify the validator credentials on EigenLayer
uint256 public stakedButNotVerifiedEth;
// operator that the Node delegator delegates to
address public delegateAddress;
/// @dev A mapping to track how much gas was spent by an address
mapping(address => uint256) public adminGasSpentInWei;
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
fallback() external {
revert InvalidCall();
}
receive() external payable {
address eigenStaking = eigenpieConfig.getContract(EigenpieConstants.EIGENPIE_STAKING);
// If Eth from Eigenstaking, then should stay waiting to be restaked;
if (msg.sender == eigenStaking) {
return;
}
uint256 gasRefunded;
address dwr = eigenpieConfig.getContract(EigenpieConstants.EIGENPIE_DWR);
// If Eth from dwr, then is partial withdraw of CL reward
if (msg.sender == dwr && adminGasSpentInWei[tx.origin] > 0) {
gasRefunded = _refundGas();
// If no funds left, return
if (msg.value == gasRefunded) {
return;
}
}
// Forward remaining balance to rewarDistributor.
// Any random eth transfer to this contract will also be treated as reward.
address rewarDistributor = eigenpieConfig.getContract(EigenpieConstants.EIGENPIE_REWADR_DISTRIBUTOR);
TransferHelper.safeTransferETH(rewarDistributor, msg.value - gasRefunded);
emit RewardsForwarded(rewarDistributor, msg.value);
}
/// @dev Initializes the contract
/// @param eigenpieConfigAddr Eigenpie config address
function initialize(address eigenpieConfigAddr) external initializer {
UtilLib.checkNonZeroAddress(eigenpieConfigAddr);
__Pausable_init();
__ReentrancyGuard_init();
eigenpieConfig = IEigenpieConfig(eigenpieConfigAddr);
emit UpdatedEigenpieConfig(eigenpieConfigAddr);
}
/// @notice Fetches balance of all assets staked in eigen layer through this contract
/// @return assets the assets that the node delegator has deposited into strategies and eth native staking.
/// @return assetBalances the balances of the assets that the node delegator has deposited into strategies
function getAssetBalances()
external
view
override
returns (address[] memory assets, uint256[] memory assetBalances)
{
address eigenlayerStrategyManagerAddress = eigenpieConfig.getContract(EigenpieConstants.EIGEN_STRATEGY_MANAGER);
return AssetManagementLib.getAssetBalances(
eigenlayerStrategyManagerAddress, stakedButNotVerifiedEth, eigenpieConfig
);
}
function getEigenPod() external view returns (address) {
return address(eigenPod);
}
/// @dev Returns the balance of an asset that the node delegator has deposited into the strategy
/// @param asset the asset to get the balance of
/// @return stakedBalance the balance of the asset
function getAssetBalance(address asset) external view override returns (uint256) {
return AssetManagementLib.getAssetBalance(eigenpieConfig, asset, stakedButNotVerifiedEth);
}
/// @dev Gets the amount of ETH staked in the EigenLayer
function getEthBalance() external view returns (uint256) {
// TODO: Once withdrawals are enabled, allow this to handle pending withdraws
IEigenPodManager eigenPodManager = AssetManagementLib.getEigenPodManager(eigenpieConfig);
return AssetManagementLib.getEthBalance(eigenPodManager, stakedButNotVerifiedEth, address(this));
}
/// @dev Triggers stopped state. Contract must not be paused.
function pause() external onlyEigenpieManager {
_pause();
}
/// @dev Returns to normal state. Contract must be paused
function unpause() external onlyDefaultAdmin {
_unpause();
}
/*//////////////////////////////////////////////////////////////
EigenLayer functions
//////////////////////////////////////////////////////////////*/
function addProofSubmitter(address _newProofSubmitter) external onlyDefaultAdmin {
address oldProofSubmitter = eigenPod.proofSubmitter();
eigenPod.setProofSubmitter(_newProofSubmitter);
emit NewProofSubmitter(oldProofSubmitter, _newProofSubmitter);
}
function startCheckpointForValidators() external whenNotPaused onlyAllowedBot {
eigenPod.startCheckpoint(true);
}
/// @notice Approves the maximum amount of an asset to the eigen strategy manager
/// @dev only supported assets can be deposited and only called by the Eigenpie manager
/// @param asset the asset to deposit
function maxApproveToEigenStrategyManager(address asset)
external
override
onlySupportedAsset(asset)
onlyEigenpieManager
{
address eigenlayerStrategyManagerAddress = eigenpieConfig.getContract(EigenpieConstants.EIGEN_STRATEGY_MANAGER);
IERC20(asset).safeApprove(eigenlayerStrategyManagerAddress, type(uint256).max);
}
/// @notice Deposits an asset lying in this NDC into its strategy
/// @dev only supported assets can be deposited and only called by the Eigenpie manager
/// @param asset the asset to deposit
function depositAssetIntoStrategy(address asset)
external
override
whenNotPaused
nonReentrant
onlySupportedAsset(asset)
onlyAllowedBot
{
AssetManagementLib.depositAssetIntoStrategy(asset, eigenpieConfig, 0, false);
}
/// @dev Sets the address to delegate tokens to in EigenLayer -- THIS CAN ONLY BE SET ONCE
function setDelegateAddress(
address _delegateAddress,
ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry,
bytes32 approverSalt
)
external
nonReentrant
onlyEigenpieManager
{
UtilLib.checkNonZeroAddress(_delegateAddress);
if (address(delegateAddress) != address(0x0)) revert DelegateAddressAlreadySet();
delegateAddress = _delegateAddress;
address delegationManagerAddr = eigenpieConfig.getContract(EigenpieConstants.EIGEN_DELEGATION_MANAGER);
IDelegationManager(delegationManagerAddr).delegateTo(delegateAddress, approverSignatureAndExpiry, approverSalt);
emit DelegationAddressUpdated(_delegateAddress);
}
function setupValidators(DepositData calldata depositData)
external
payable
whenNotPaused
nonReentrant
onlyAllowedBot
{
_makeBeaconDeposit(depositData.publicKeys, depositData.signatures, depositData.depositDataRoots);
}
function setupSSVNetwork(
DepositData calldata depositData,
SSVPayload calldata ssvPayload
)
external
payable
whenNotPaused
nonReentrant
onlyAllowedBot
{
_makeBeaconDeposit(depositData.publicKeys, depositData.signatures, depositData.depositDataRoots);
ValidatorLib.bulkRegisterValidator(
depositData.publicKeys,
ssvPayload.operatorIds,
ssvPayload.sharesData,
ssvPayload.amount,
ssvPayload.cluster,
eigenpieConfig
);
}
/// @dev Verifies the withdrawal credentials for a withdrawal
/// This will allow the EigenPodManager to verify the withdrawal credentials and credit the Node delegators with
/// shares
function verifyWithdrawalCredentials(
uint64 beaconTimestamp,
BeaconChainProofs.StateRootProof calldata stateRootProof,
uint40[] calldata validatorIndices,
bytes[] calldata validatorFieldsProofs,
bytes32[][] calldata validatorFields
)
external
whenNotPaused
onlyAllowedBot
{
uint256 gasBefore = gasleft();
stakedButNotVerifiedEth -= ValidatorLib.verifyWithdrawalCredentials(
eigenPod, beaconTimestamp, stateRootProof, validatorIndices, validatorFieldsProofs, validatorFields
);
// update the gas spent for RestakeAdmin
_recordGas(gasBefore);
}
/**
* @notice Verify many Withdrawals and process them in the EigenPod
* @dev For each withdrawal (partial or full), verify it in the EigenPod
*/
function verifyCheckpointProofs(
BeaconChainProofs.BalanceContainerProof calldata balanceContainerProof,
BeaconChainProofs.BalanceProof[] calldata proofs
)
external
whenNotPaused
onlyAllowedBot
{
uint256 gasBefore = gasleft();
eigenPod.verifyCheckpointProofs(balanceContainerProof, proofs);
// update the gas spent for RestakeAdmin
_recordGas(gasBefore);
}
function queueWithdrawalToEigenLayer(
address[] memory assets,
uint256[] memory amounts
)
external
whenNotPaused
nonReentrant
onlyAllowedBot
{
ValidatorLib.queueWithdrawalToEigenLayer(eigenpieConfig, assets, amounts, amounts);
}
function completeAssetWithdrawalFromEigenLayer(
IDelegationManager.Withdrawal calldata withdrawal,
IERC20[] calldata tokens,
uint256 middlewareTimesIndex,
bool receiveAsTokens
)
external
whenNotPaused
nonReentrant
onlyAllowedBot
{
address eigenpieWithdrawManager = eigenpieConfig.getContract(EigenpieConstants.EIGENPIE_WITHDRAW_MANAGER);
address delegationManagerAddr = eigenpieConfig.getContract(EigenpieConstants.EIGEN_DELEGATION_MANAGER);
ValidatorLib.completeAssetWithdrawalFromEigenLayer(
withdrawal, tokens, middlewareTimesIndex, receiveAsTokens, eigenpieWithdrawManager, delegationManagerAddr
);
}
//The DepositManager will be the pod owner in the EigenPodManager contract
function createEigenPod() external override onlyDefaultAdmin {
if (address(eigenPod) != address(0)) {
revert EigenPodExisted();
}
IEigenPodManager eigenPodManager = AssetManagementLib.getEigenPodManager(eigenpieConfig);
IEigenPodManager(eigenPodManager).createPod();
eigenPod = eigenPodManager.getPod(address(this));
emit EigenPodCreated(address(eigenPod));
}
function transferAssetToEigenStaking(address asset, uint256 amount) external onlyEigenpieManager {
address eigenStaking = eigenpieConfig.getContract(EigenpieConstants.EIGENPIE_STAKING);
TransferHelper.safeTransferToken(asset, eigenStaking, amount);
}
/*//////////////////////////////////////////////////////////////
SSV Network functions
//////////////////////////////////////////////////////////////*/
/// @notice Registers new validators on the SSV Network
function bulkRegisterValidator(
bytes[] calldata publicKeys,
uint64[] calldata operatorIds,
bytes[] calldata sharesData,
uint256 amount,
ISSVNetworkCore.Cluster memory cluster
)
external
onlyEigenpieManager
{
ValidatorLib.bulkRegisterValidator(publicKeys, operatorIds, sharesData, amount, cluster, eigenpieConfig);
}
/// @notice Fires the exit event for a set of validators
function bulkExitValidator(
bytes[] calldata publicKeys,
uint64[] calldata operatorIds
)
external
onlyEigenpieManager
{
address ssvNetwork = eigenpieConfig.getContract(EigenpieConstants.SSVNETWORK_ENTRY);
ISSVClusters(ssvNetwork).bulkExitValidator(publicKeys, operatorIds);
}
/// @notice Bulk removes a set of existing validators in the same cluster from the SSV Network
/// @notice Reverts if publicKeys contains duplicates or non-existent validators
function bulkRemoveValidator(
bytes[] calldata publicKeys,
uint64[] memory operatorIds,
ISSVNetworkCore.Cluster memory cluster
)
external
onlyEigenpieManager
{
address ssvNetwork = eigenpieConfig.getContract(EigenpieConstants.SSVNETWORK_ENTRY);
ISSVClusters(ssvNetwork).bulkRemoveValidator(publicKeys, operatorIds, cluster);
}
function setFeeRecipientAddress(address feeRecipientAddress) external onlyEigenpieManager {
address ssvNetwork = eigenpieConfig.getContract(EigenpieConstants.SSVNETWORK_ENTRY);
ISSVNetwork(ssvNetwork).setFeeRecipientAddress(feeRecipientAddress);
}
function deposit(
uint64[] memory operatorIds,
uint256 amount,
ISSVNetworkCore.Cluster memory cluster
)
external
onlyEigenpieManager
{
address ssvNetwork = eigenpieConfig.getContract(EigenpieConstants.SSVNETWORK_ENTRY);
ISSVClusters(ssvNetwork).deposit(address(this), operatorIds, amount, cluster);
}
function withdraw(
uint64[] memory operatorIds,
uint256 tokenAmount,
ISSVNetworkCore.Cluster memory cluster
)
external
onlyEigenpieManager
{
address ssvNetwork = eigenpieConfig.getContract(EigenpieConstants.SSVNETWORK_ENTRY);
ISSVClusters(ssvNetwork).withdraw(operatorIds, tokenAmount, cluster);
}
/*//////////////////////////////////////////////////////////////
Internal functions
//////////////////////////////////////////////////////////////*/
/**
* @notice Adds the amount of gas spent for an account
* @dev Tracks for later redemption from rewards coming from the DWR
* @param initialGas .
*/
function _recordGas(uint256 initialGas) internal {
uint256 gasSpent = AssetManagementLib.calculateGasSpent(initialGas, eigenpieConfig, tx.gasprice);
adminGasSpentInWei[msg.sender] += gasSpent;
emit GasSpent(msg.sender, gasSpent);
}
/**
* @notice Send owed refunds to the admin
* @dev .
* @return uint256 .
*/
function _refundGas() internal returns (uint256) {
uint256 gasRefund = AssetManagementLib.calculateAndTransferRefundGas(tx.origin, adminGasSpentInWei[tx.origin]);
// reset gas spent by admin
adminGasSpentInWei[tx.origin] -= gasRefund;
emit GasRefunded(tx.origin, gasRefund);
return gasRefund;
}
function _makeBeaconDeposit(
bytes[] memory publicKeys,
bytes[] memory signatures,
bytes32[] memory depositDataRoots
)
internal
{
ValidatorLib.makeBeaconDeposit(publicKeys, signatures, depositDataRoots, eigenpieConfig, address(eigenPod));
stakedButNotVerifiedEth += publicKeys.length * EigenpieConstants.DEPOSIT_AMOUNT;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
pragma solidity ^0.8.0;
import "../utils/ContextUpgradeable.sol";
import "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
bool private _paused;
/**
* @dev Initializes the contract in unpaused state.
*/
function __Pausable_init() internal onlyInitializing {
__Pausable_init_unchained();
}
function __Pausable_init_unchained() internal onlyInitializing {
_paused = false;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
require(!paused(), "Pausable: paused");
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
require(paused(), "Pausable: not paused");
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
import "../proxy/utils/Initializable.sol";
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuardUpgradeable is Initializable {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
function __ReentrancyGuard_init() internal onlyInitializing {
__ReentrancyGuard_init_unchained();
}
function __ReentrancyGuard_init_unchained() internal onlyInitializing {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be _NOT_ENTERED
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == _ENTERED;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/IERC20Permit.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(IERC20 token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
* Revert on invalid signature.
*/
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return
success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./UtilLib.sol";
library TransferHelper {
using UtilLib for address;
function safeTransferToken(address token, address to, uint256 value) internal {
if (token.isNativeToken()) {
safeTransferETH(to, value);
} else {
safeTransfer(IERC20(token), to, value);
}
}
function safeTransferETH(address to, uint256 value) internal {
(bool success,) = address(to).call{ value: value }("");
require(success, "TransferHelper: Sending ETH failed");
}
function balanceOf(address token, address addr) internal view returns (uint256) {
if (token.isNativeToken()) {
return addr.balance;
} else {
return IERC20(token).balanceOf(addr);
}
}
function safeTransfer(IERC20 token, address to, uint256 value) internal {
// bytes4(keccak256(bytes('transfer(address,uint256)'))) -> 0xa9059cbb
(bool success, bytes memory data) = address(token).call(abi.encodeWithSelector(0xa9059cbb, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))), "TransferHelper::safeTransfer: transfer failed"
);
}
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
// bytes4(keccak256(bytes('transferFrom(address,address,uint256)'))) -> 0x23b872dd
(bool success, bytes memory data) = address(token).call(abi.encodeWithSelector(0x23b872dd, from, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
"TransferHelper::safeTransferFrom: transfer failed"
);
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.21;
import { EigenpieConstants } from "./EigenpieConstants.sol";
/// @title UtilLib - Utility library
/// @notice Utility functions
library UtilLib {
error ZeroAddressNotAllowed();
/// @dev zero address check modifier
/// @param address_ address to check
function checkNonZeroAddress(address address_) internal pure {
if (address_ == address(0)) revert ZeroAddressNotAllowed();
}
function isNativeToken(address addr) internal pure returns (bool) {
return addr == EigenpieConstants.PLATFORM_TOKEN_ADDRESS;
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.21;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { EigenpieConstants } from "../utils/EigenpieConstants.sol";
import { BeaconChainProofs } from "../utils/external/BeaconChainProofs.sol";
import { ISSVClusters } from "../interfaces/ssvNetwork/ISSVClusters.sol";
import { ISSVNetworkCore } from "../interfaces/ssvNetwork/ISSVNetworkCore.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IBeaconDepositContract } from "../interfaces/IBeaconDepositContract.sol";
import { IDelegationManager } from "../interfaces/eigenlayer/IDelegationManager.sol";
import { INodeDelegator } from "../interfaces/INodeDelegator.sol";
import { IStrategy } from "../interfaces/eigenlayer/IStrategy.sol";
import { IEigenpieConfig } from "../utils/EigenpieConfigRoleChecker.sol";
import { IEigenPod } from "../interfaces/eigenlayer/IEigenPodManager.sol";
// A library that handles the operations associated with validators, like registration and deposit handling. This
// library will encapsulate the logic for interactions with the Beacon chain and SSV network.
library ValidatorLib {
using SafeERC20 for IERC20;
event WithdrawalQueuedToEigenLayer(
bytes32[] withdrawalRoot, IStrategy[] strategies, address[] assets, uint256[] amounts, uint256 blockNumber
);
function makeBeaconDeposit(
bytes[] memory publicKeys,
bytes[] memory signatures,
bytes32[] memory depositDataRoots,
IEigenpieConfig eigenpieConfig,
address eigenPod
)
external
{
// sanity checks
uint256 count = depositDataRoots.length;
if (count == 0) revert INodeDelegator.AtLeastOneValidator();
if (count >= EigenpieConstants.MAX_VALIDATORS) {
revert INodeDelegator.MaxValidatorsInput();
}
if (publicKeys.length != count) {
revert INodeDelegator.PublicKeyNotMatch();
}
if (signatures.length != count) {
revert INodeDelegator.SignaturesNotMatch();
}
address depositContract = eigenpieConfig.getContract(EigenpieConstants.BEACON_DEPOSIT);
bytes memory withdrawalCredentials = getEigenPodwithdrawCredential(eigenPod);
for (uint256 i = 0; i < count;) {
IBeaconDepositContract(depositContract).deposit{ value: EigenpieConstants.DEPOSIT_AMOUNT }(
publicKeys[i], withdrawalCredentials, signatures[i], depositDataRoots[i]
);
unchecked {
++i;
}
}
}
/// @notice Registers new validators on the SSV Network
function bulkRegisterValidator(
bytes[] calldata publicKeys,
uint64[] calldata operatorIds,
bytes[] calldata sharesData,
uint256 amount,
ISSVNetworkCore.Cluster memory cluster,
IEigenpieConfig eigenpieConfig
)
external
{
address ssvToken = eigenpieConfig.getContract(EigenpieConstants.SSV_TOKEN);
address ssvNetwork = eigenpieConfig.getContract(EigenpieConstants.SSVNETWORK_ENTRY);
IERC20(ssvToken).approve(ssvNetwork, amount);
ISSVClusters(ssvNetwork).bulkRegisterValidator(publicKeys, operatorIds, sharesData, amount, cluster);
}
function getEigenPodwithdrawCredential(address eigenPod) public pure returns (bytes memory) {
if (eigenPod == address(0)) revert INodeDelegator.EigenPodExisted();
bytes memory withdrawalCredentials = abi.encodePacked(hex"010000000000000000000000", eigenPod);
return withdrawalCredentials;
}
function verifyWithdrawalCredentials(
IEigenPod eigenPod,
uint64 beaconTimestamp,
BeaconChainProofs.StateRootProof calldata stateRootProof,
uint40[] calldata validatorIndices,
bytes[] calldata validatorFieldsProofs,
bytes32[][] calldata validatorFields
)
external
returns (uint256 stakedButNotVerifiedEth)
{
eigenPod.verifyWithdrawalCredentials(
beaconTimestamp, stateRootProof, validatorIndices, validatorFieldsProofs, validatorFields
);
// Decrement the staked but not verified ETH
for (uint256 i = 0; i < validatorFields.length;) {
uint64 validatorCurrentBalanceGwei = BeaconChainProofs.getEffectiveBalanceGwei(validatorFields[i]);
stakedButNotVerifiedEth += (validatorCurrentBalanceGwei * EigenpieConstants.GWEI_TO_WEI);
unchecked {
++i;
}
}
}
function queueWithdrawalToEigenLayer(
IEigenpieConfig eigenpieConfig,
address[] memory assets,
uint256[] memory amounts,
uint256[] memory lstToWithdraw
)
external
{
address delegationManagerAddr = eigenpieConfig.getContract(EigenpieConstants.EIGEN_DELEGATION_MANAGER);
IDelegationManager.QueuedWithdrawalParams[] memory withdrawParams =
new IDelegationManager.QueuedWithdrawalParams[](1);
withdrawParams[0].strategies = new IStrategy[](assets.length);
withdrawParams[0].shares = new uint256[](assets.length);
withdrawParams[0].withdrawer = address(this);
for (uint256 i = 0; i < assets.length;) {
address strategy = eigenpieConfig.assetStrategy(assets[i]);
withdrawParams[0].strategies[i] = IStrategy(strategy);
if (assets[i] == EigenpieConstants.PLATFORM_TOKEN_ADDRESS) {
withdrawParams[0].shares[i] = amounts[i];
} else {
withdrawParams[0].shares[i] = IStrategy(strategy).underlyingToShares(lstToWithdraw[i]);
}
unchecked {
++i;
}
}
bytes32[] memory withdrawalRoot = IDelegationManager(delegationManagerAddr).queueWithdrawals(withdrawParams);
emit INodeDelegator.WithdrawalQueuedToEigenLayer(
withdrawalRoot, withdrawParams[0].strategies, assets, amounts, block.number
);
}
function completeAssetWithdrawalFromEigenLayer(
IDelegationManager.Withdrawal calldata withdrawal,
IERC20[] calldata tokens,
uint256 middlewareTimesIndex,
bool receiveAsTokens,
address recipient,
address delegationManagerAddr
)
external
{
uint256[] memory beforeAmounts = new uint256[](tokens.length);
// Record the balance before the withdrawal
for (uint256 i = 0; i < tokens.length;) {
if (address(tokens[i]) != EigenpieConstants.PLATFORM_TOKEN_ADDRESS) {
beforeAmounts[i] = tokens[i].balanceOf(address(this));
}
unchecked {
++i;
}
}
// Complete the queued withdrawal
IDelegationManager(delegationManagerAddr).completeQueuedWithdrawal(
withdrawal, tokens, middlewareTimesIndex, receiveAsTokens
);
// Transfer the withdrawn amounts to the recipient
for (uint256 i = 0; i < tokens.length;) {
if (address(tokens[i]) != EigenpieConstants.PLATFORM_TOKEN_ADDRESS) {
uint256 afterBalance = tokens[i].balanceOf(address(this));
IERC20(tokens[i]).safeTransfer(recipient, afterBalance - beforeAmounts[i]);
}
unchecked {
++i;
}
}
}
}
{
"compilationTarget": {
"contracts/NodeDelegator.sol": "NodeDelegator"
},
"evmVersion": "shanghai",
"libraries": {
"contracts/libraries/AssetManagementLib.sol:AssetManagementLib": "0x5d19f4e3dd339ba0f6770b1a2d6235a26aa42037",
"contracts/libraries/ValidatorLib.sol:ValidatorLib": "0x249ca69c51981e37a78b02273501a5a0649d31de"
},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 10000
},
"remappings": [
":@arbitrum/=node_modules/@arbitrum/",
":@chainlink/=node_modules/@chainlink/",
":@eth-optimism/=node_modules/@chainlink/contracts/node_modules/@eth-optimism/",
":@offchainlabs/=node_modules/@offchainlabs/",
":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
":@scroll-tech/=node_modules/@scroll-tech/",
":ds-test/=lib/forge-std/lib/ds-test/src/",
":erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/",
":eth-gas-reporter/=node_modules/eth-gas-reporter/",
":forge-std/=lib/forge-std/src/",
":hardhat/=node_modules/hardhat/",
":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/",
":openzeppelin/=lib/openzeppelin-contracts-upgradeable/contracts/",
":solidity-code-metrics/=node_modules/solidity-code-metrics/"
]
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AssetNotSupported","type":"error"},{"inputs":[],"name":"AtLeastOneValidator","type":"error"},{"inputs":[],"name":"CallerNotEigenpieConfigAdmin","type":"error"},{"inputs":[],"name":"CallerNotEigenpieConfigAllowedBot","type":"error"},{"inputs":[],"name":"CallerNotEigenpieConfigManager","type":"error"},{"inputs":[],"name":"DelegateAddressAlreadySet","type":"error"},{"inputs":[],"name":"EigenPodExisted","type":"error"},{"inputs":[],"name":"InvalidAmount","type":"error"},{"inputs":[],"name":"InvalidCall","type":"error"},{"inputs":[],"name":"InvalidCaller","type":"error"},{"inputs":[],"name":"MaxValidatorsInput","type":"error"},{"inputs":[],"name":"NoPubKeysProvided","type":"error"},{"inputs":[],"name":"PublicKeyNotMatch","type":"error"},{"inputs":[],"name":"SignaturesNotMatch","type":"error"},{"inputs":[],"name":"StrategyIsNotSetForAsset","type":"error"},{"inputs":[],"name":"TokenTransferFailed","type":"error"},{"inputs":[],"name":"ZeroAddressNotAllowed","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"asset","type":"address"},{"indexed":true,"internalType":"address","name":"strategy","type":"address"},{"indexed":false,"internalType":"uint256","name":"depositAmount","type":"uint256"}],"name":"AssetDepositIntoStrategy","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"delegate","type":"address"}],"name":"DelegationAddressUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"createdEigenPod","type":"address"}],"name":"EigenPodCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"gasRefund","type":"uint256"}],"name":"GasRefunded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"gasUsed","type":"uint256"}],"name":"GasSpent","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldProofSubmitter","type":"address"},{"indexed":true,"internalType":"address","name":"_newProofSubmitter","type":"address"}],"name":"NewProofSubmitter","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"destinatino","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"RewardsForwarded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"eigenpieConfig","type":"address"}],"name":"UpdatedEigenpieConfig","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32[]","name":"withdrawalRoot","type":"bytes32[]"},{"indexed":false,"internalType":"contract IStrategy[]","name":"strategies","type":"address[]"},{"indexed":false,"internalType":"address[]","name":"assets","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"withdrawalAmounts","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"startBlock","type":"uint256"}],"name":"WithdrawalQueuedToEigenLayer","type":"event"},{"stateMutability":"nonpayable","type":"fallback"},{"inputs":[{"internalType":"address","name":"_newProofSubmitter","type":"address"}],"name":"addProofSubmitter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"adminGasSpentInWei","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"publicKeys","type":"bytes[]"},{"internalType":"uint64[]","name":"operatorIds","type":"uint64[]"}],"name":"bulkExitValidator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"publicKeys","type":"bytes[]"},{"internalType":"uint64[]","name":"operatorIds","type":"uint64[]"},{"internalType":"bytes[]","name":"sharesData","type":"bytes[]"},{"internalType":"uint256","name":"amount","type":"uint256"},{"components":[{"internalType":"uint32","name":"validatorCount","type":"uint32"},{"internalType":"uint64","name":"networkFeeIndex","type":"uint64"},{"internalType":"uint64","name":"index","type":"uint64"},{"internalType":"bool","name":"active","type":"bool"},{"internalType":"uint256","name":"balance","type":"uint256"}],"internalType":"struct ISSVNetworkCore.Cluster","name":"cluster","type":"tuple"}],"name":"bulkRegisterValidator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"publicKeys","type":"bytes[]"},{"internalType":"uint64[]","name":"operatorIds","type":"uint64[]"},{"components":[{"internalType":"uint32","name":"validatorCount","type":"uint32"},{"internalType":"uint64","name":"networkFeeIndex","type":"uint64"},{"internalType":"uint64","name":"index","type":"uint64"},{"internalType":"bool","name":"active","type":"bool"},{"internalType":"uint256","name":"balance","type":"uint256"}],"internalType":"struct ISSVNetworkCore.Cluster","name":"cluster","type":"tuple"}],"name":"bulkRemoveValidator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"staker","type":"address"},{"internalType":"address","name":"delegatedTo","type":"address"},{"internalType":"address","name":"withdrawer","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint32","name":"startBlock","type":"uint32"},{"internalType":"contract IStrategy[]","name":"strategies","type":"address[]"},{"internalType":"uint256[]","name":"shares","type":"uint256[]"}],"internalType":"struct IDelegationManager.Withdrawal","name":"withdrawal","type":"tuple"},{"internalType":"contract IERC20[]","name":"tokens","type":"address[]"},{"internalType":"uint256","name":"middlewareTimesIndex","type":"uint256"},{"internalType":"bool","name":"receiveAsTokens","type":"bool"}],"name":"completeAssetWithdrawalFromEigenLayer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"createEigenPod","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"delegateAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64[]","name":"operatorIds","type":"uint64[]"},{"internalType":"uint256","name":"amount","type":"uint256"},{"components":[{"internalType":"uint32","name":"validatorCount","type":"uint32"},{"internalType":"uint64","name":"networkFeeIndex","type":"uint64"},{"internalType":"uint64","name":"index","type":"uint64"},{"internalType":"bool","name":"active","type":"bool"},{"internalType":"uint256","name":"balance","type":"uint256"}],"internalType":"struct ISSVNetworkCore.Cluster","name":"cluster","type":"tuple"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"depositAssetIntoStrategy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"eigenPod","outputs":[{"internalType":"contract IEigenPod","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"eigenpieConfig","outputs":[{"internalType":"contract IEigenpieConfig","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"getAssetBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAssetBalances","outputs":[{"internalType":"address[]","name":"assets","type":"address[]"},{"internalType":"uint256[]","name":"assetBalances","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getEigenPod","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getEthBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"eigenpieConfigAddr","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"maxApproveToEigenStrategyManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"assets","type":"address[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"queueWithdrawalToEigenLayer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_delegateAddress","type":"address"},{"components":[{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"uint256","name":"expiry","type":"uint256"}],"internalType":"struct ISignatureUtils.SignatureWithExpiry","name":"approverSignatureAndExpiry","type":"tuple"},{"internalType":"bytes32","name":"approverSalt","type":"bytes32"}],"name":"setDelegateAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"feeRecipientAddress","type":"address"}],"name":"setFeeRecipientAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes[]","name":"publicKeys","type":"bytes[]"},{"internalType":"bytes[]","name":"signatures","type":"bytes[]"},{"internalType":"bytes32[]","name":"depositDataRoots","type":"bytes32[]"}],"internalType":"struct INodeDelegator.DepositData","name":"depositData","type":"tuple"},{"components":[{"internalType":"uint64[]","name":"operatorIds","type":"uint64[]"},{"internalType":"bytes[]","name":"sharesData","type":"bytes[]"},{"internalType":"uint256","name":"amount","type":"uint256"},{"components":[{"internalType":"uint32","name":"validatorCount","type":"uint32"},{"internalType":"uint64","name":"networkFeeIndex","type":"uint64"},{"internalType":"uint64","name":"index","type":"uint64"},{"internalType":"bool","name":"active","type":"bool"},{"internalType":"uint256","name":"balance","type":"uint256"}],"internalType":"struct ISSVNetworkCore.Cluster","name":"cluster","type":"tuple"}],"internalType":"struct INodeDelegator.SSVPayload","name":"ssvPayload","type":"tuple"}],"name":"setupSSVNetwork","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes[]","name":"publicKeys","type":"bytes[]"},{"internalType":"bytes[]","name":"signatures","type":"bytes[]"},{"internalType":"bytes32[]","name":"depositDataRoots","type":"bytes32[]"}],"internalType":"struct INodeDelegator.DepositData","name":"depositData","type":"tuple"}],"name":"setupValidators","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"stakedButNotVerifiedEth","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"startCheckpointForValidators","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferAssetToEigenStaking","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"eigenpieConfigAddr","type":"address"}],"name":"updateEigenpieConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32","name":"balanceContainerRoot","type":"bytes32"},{"internalType":"bytes","name":"proof","type":"bytes"}],"internalType":"struct BeaconChainProofs.BalanceContainerProof","name":"balanceContainerProof","type":"tuple"},{"components":[{"internalType":"bytes32","name":"pubkeyHash","type":"bytes32"},{"internalType":"bytes32","name":"balanceRoot","type":"bytes32"},{"internalType":"bytes","name":"proof","type":"bytes"}],"internalType":"struct BeaconChainProofs.BalanceProof[]","name":"proofs","type":"tuple[]"}],"name":"verifyCheckpointProofs","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"beaconTimestamp","type":"uint64"},{"components":[{"internalType":"bytes32","name":"beaconStateRoot","type":"bytes32"},{"internalType":"bytes","name":"proof","type":"bytes"}],"internalType":"struct BeaconChainProofs.StateRootProof","name":"stateRootProof","type":"tuple"},{"internalType":"uint40[]","name":"validatorIndices","type":"uint40[]"},{"internalType":"bytes[]","name":"validatorFieldsProofs","type":"bytes[]"},{"internalType":"bytes32[][]","name":"validatorFields","type":"bytes32[][]"}],"name":"verifyWithdrawalCredentials","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64[]","name":"operatorIds","type":"uint64[]"},{"internalType":"uint256","name":"tokenAmount","type":"uint256"},{"components":[{"internalType":"uint32","name":"validatorCount","type":"uint32"},{"internalType":"uint64","name":"networkFeeIndex","type":"uint64"},{"internalType":"uint64","name":"index","type":"uint64"},{"internalType":"bool","name":"active","type":"bool"},{"internalType":"uint256","name":"balance","type":"uint256"}],"internalType":"struct ISSVNetworkCore.Cluster","name":"cluster","type":"tuple"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]