Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EigenLayer Slashing] (1) update EtherFiRestaker, (2) fixes in EtherFiNode #222

Merged
23 changes: 18 additions & 5 deletions src/EtherFiNode.sol
Original file line number Diff line number Diff line change
Expand Up @@ -318,11 +318,12 @@ contract EtherFiNode is IEtherFiNode, IERC1271 {

if (isRestakingEnabled) {
IDelegationManager delegationManager = IEtherFiNodesManager(etherFiNodesManager).delegationManager();
IStrategy beaconStrategy = delegationManager.beaconChainETHStrategy();

// get the shares locked in the EigenPod
// - `withdrawableShares` reflects the slashing on 'depositShares'
(uint256[] memory withdrawableShares, uint256[] memory depositShares) = delegationManager.getWithdrawableShares(address(this), getStrategies());
_eigenPod = withdrawableShares[0];
_eigenPod = beaconStrategy.sharesToUnderlyingView(withdrawableShares[0]);

// get the shares locked in the DelegationManager
// - `shares` reflects the slashing. but it can change over time while in the queue until the slashing completion
Expand All @@ -333,6 +334,7 @@ contract EtherFiNode is IEtherFiNode, IERC1271 {
_withdrawal_queue += shares[i][j];
}
}
_withdrawal_queue = beaconStrategy.sharesToUnderlyingView(_withdrawal_queue);
}
return (_withdrawalSafe, _eigenPod, _withdrawal_queue);
}
Expand Down Expand Up @@ -603,11 +605,22 @@ contract EtherFiNode is IEtherFiNode, IERC1271 {
IStrategy[] memory strategies = getStrategies();
(uint256[] memory withdrawableShares, uint256[] memory depositShares) = delegationManager.getWithdrawableShares(address(this), strategies);

// calculate the amount to withdraw
// as this is per validator withdrawal, we cap the withdrawal amount to 32 ether
// in the case of slashing, the withdrawals for the last few validators might have less than 32 ether
// calculate the amount to withdraw:
// if the withdrawal is for the last validator:
// withdraw the full balance including staking rewards
// else
// as this is per validator withdrawal, we cap the withdrawal amount to 32 ether
// in the case of slashing, the withdrawals for the last few validators might have less than 32 ether
//
// TODO: revisit for Pectra where a validator can have more than 32 ether
uint256 depositSharesToWithdraw = Math.min(depositShares[0], 32 ether);
uint256 depositSharesToWithdraw;
if (numAssociatedValidators() == 1) {
require(IEigenPod(eigenPod).activeValidatorCount() == 0, "ACTIVE_VALIDATOR_EXISTS");
depositSharesToWithdraw = depositShares[0];
} else {
uint256 eigenLayerBeaconStrategyShare = delegationManager.beaconChainETHStrategy().underlyingToShares(32 ether);
depositSharesToWithdraw = Math.min(depositShares[0], eigenLayerBeaconStrategyShare);
}

// Queue the withdrawal
// Note that the actual withdarwal amount can change if the slashing happens
Expand Down
212 changes: 74 additions & 138 deletions src/EtherFiRestaker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import "./LiquidityPool.sol";

import "./eigenlayer-interfaces/IStrategyManager.sol";
import "./eigenlayer-interfaces/IDelegationManager.sol";
import "./eigenlayer-interfaces/IRewardsCoordinator.sol";

contract EtherFiRestaker is Initializable, UUPSUpgradeable, OwnableUpgradeable, PausableUpgradeable {
using SafeERC20 for IERC20;
Expand All @@ -26,6 +27,8 @@ contract EtherFiRestaker is Initializable, UUPSUpgradeable, OwnableUpgradeable,
uint256 elSharesInPendingForWithdrawals;
}

IRewardsCoordinator public immutable rewardsCoordinator;

LiquidityPool public liquidityPool;
Liquifier public liquifier;
ILidoWithdrawalQueue public lidoWithdrawalQueue;
Expand All @@ -39,7 +42,7 @@ contract EtherFiRestaker is Initializable, UUPSUpgradeable, OwnableUpgradeable,
mapping(address => TokenInfo) public tokenInfos;

EnumerableSet.Bytes32Set private withdrawalRootsSet;
mapping(bytes32 => IDelegationManager.Withdrawal) public withdrawalRootToWithdrawal;
mapping(bytes32 => IDelegationManager.Withdrawal) public DEPRECATED_withdrawalRootToWithdrawal;


event QueuedStEthWithdrawals(uint256[] _reqIds);
Expand All @@ -56,7 +59,8 @@ contract EtherFiRestaker is Initializable, UUPSUpgradeable, OwnableUpgradeable,
error IncorrectCaller();

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
constructor(address _rewardsCoordinator) {
rewardsCoordinator = IRewardsCoordinator(_rewardsCoordinator);
_disableInitializers();
}

Expand Down Expand Up @@ -137,6 +141,11 @@ contract EtherFiRestaker is Initializable, UUPSUpgradeable, OwnableUpgradeable,
// |--------------------------------------------------------------------------------------------|
// | EigenLayer Restaking |
// |--------------------------------------------------------------------------------------------|

/// Set the claimer of the restaking rewards of this contract
function setRewardsClaimer(address _claimer) external onlyAdmin {
rewardsCoordinator.setClaimerFor(_claimer);
}

// delegate to an AVS operator
function delegateTo(address operator, IDelegationManager.SignatureWithExpiry memory approverSignatureAndExpiry, bytes32 approverSalt) external onlyAdmin {
Expand All @@ -145,19 +154,13 @@ contract EtherFiRestaker is Initializable, UUPSUpgradeable, OwnableUpgradeable,

// undelegate from the current AVS operator & un-restake all
function undelegate() external onlyAdmin returns (bytes32[] memory) {
revert("FIX BELOW");

// Un-restake all assets
// Currently, only stETH is supported
// TokenInfo memory info = tokenInfos[address(lido)];
// uint256 shares = eigenLayerStrategyManager.stakerStrategyShares(address(this), info.elStrategy);

// _queueWithdrawlsByShares(address(lido), shares);
bytes32[] memory withdrawalRoots = eigenLayerDelegationManager.undelegate(address(this));

// bytes32[] memory withdrawalRoots = eigenLayerDelegationManager.undelegate(address(this));
// assert(withdrawalRoots.length == 0);

// return withdrawalRoots;
for (uint256 i = 0; i < withdrawalRoots.length; i++) {
withdrawalRootsSet.add(withdrawalRoots[i]);
}

return withdrawalRoots;
}

// deposit the token in holding into the restaking strategy
Expand All @@ -176,115 +179,41 @@ contract EtherFiRestaker is Initializable, UUPSUpgradeable, OwnableUpgradeable,
/// @param amount the amount of token to withdraw
function queueWithdrawals(address token, uint256 amount) public onlyAdmin returns (bytes32[] memory) {
uint256 shares = getEigenLayerRestakingStrategy(token).underlyingToSharesView(amount);
return _queueWithdrawlsByShares(token, shares);
return _queueWithdrawalsByShares(token, shares);
}

/// Advanced version
function queueWithdrawals(IDelegationManager.QueuedWithdrawalParams[] memory queuedWithdrawalParams) public onlyAdmin returns (bytes32[] memory) {
revert("FIX BELOW");
// uint256 currentNonce = eigenLayerDelegationManager.cumulativeWithdrawalsQueued(address(this));

// bytes32[] memory withdrawalRoots = eigenLayerDelegationManager.queueWithdrawals(queuedWithdrawalParams);
// IDelegationManager.Withdrawal[] memory withdrawals = new IDelegationManager.Withdrawal[](queuedWithdrawalParams.length);

// for (uint256 i = 0; i < queuedWithdrawalParams.length; i++) {
// withdrawals[i] = IDelegationManager.Withdrawal({
// staker: address(this),
// delegatedTo: eigenLayerDelegationManager.delegatedTo(address(this)),
// withdrawer: address(this),
// nonce: currentNonce + i,
// startBlock: uint32(block.number),
// strategies: queuedWithdrawalParams[i].strategies,
// shares: queuedWithdrawalParams[i].shares
// });

// require(eigenLayerDelegationManager.calculateWithdrawalRoot(withdrawals[i]) == withdrawalRoots[i], "INCORRECT_WITHDRAWAL_ROOT");
// require(eigenLayerDelegationManager.pendingWithdrawals(withdrawalRoots[i]), "WITHDRAWAL_NOT_PENDING");

// for (uint256 j = 0; j < queuedWithdrawalParams[i].strategies.length; j++) {
// address token = address(queuedWithdrawalParams[i].strategies[j].underlyingToken());
// tokenInfos[token].elSharesInPendingForWithdrawals += queuedWithdrawalParams[i].shares[j];
// }

// withdrawalRootToWithdrawal[withdrawalRoots[i]] = withdrawals[i];
// withdrawalRootsSet.add(withdrawalRoots[i]);
// }

// return withdrawalRoots;
}

/// @notice Complete the queued withdrawals that are ready to be withdrawn
/// @param max_cnt the maximum number of withdrawals to complete
function completeQueuedWithdrawals(uint256 max_cnt) external onlyAdmin {
revert("FIX BELOW");
// bytes32[] memory withdrawalRoots = pendingWithdrawalRoots();

// // process the first `max_cnt` withdrawals
// uint256 num_to_process = _min(max_cnt, withdrawalRoots.length);

// IDelegationManager.Withdrawal[] memory _queuedWithdrawals = new IDelegationManager.Withdrawal[](num_to_process);
// IERC20[][] memory _tokens = new IERC20[][](num_to_process);
// uint256[] memory _middlewareTimesIndexes = new uint256[](num_to_process);
function queueWithdrawalsWithParams(IDelegationManagerTypes.QueuedWithdrawalParams[] memory params) public onlyAdmin returns (bytes32[] memory) {
bytes32[] memory withdrawalRoots = eigenLayerDelegationManager.queueWithdrawals(params);

// uint256 cnt = 0;
// for (uint256 i = 0; i < num_to_process; i++) {
// IDelegationManager.Withdrawal memory withdrawal = withdrawalRootToWithdrawal[withdrawalRoots[i]];

// uint256 withdrawalDelay = eigenLayerDelegationManager.getWithdrawalDelay(withdrawal.strategies);

// if (withdrawal.startBlock + withdrawalDelay <= block.number) {
// IERC20[] memory tokens = new IERC20[](withdrawal.strategies.length);
// for (uint256 j = 0; j < withdrawal.strategies.length; j++) {
// tokens[j] = withdrawal.strategies[j].underlyingToken();

// assert(tokenInfos[address(tokens[j])].elStrategy == withdrawal.strategies[j]);

// tokenInfos[address(tokens[j])].elSharesInPendingForWithdrawals -= withdrawal.shares[j];
// }

// _queuedWithdrawals[cnt] = withdrawal;
// _tokens[cnt] = tokens;
// _middlewareTimesIndexes[cnt] = 0;
// cnt += 1;
// }
// }

// if (cnt == 0) return;

// assembly {
// mstore(_queuedWithdrawals, cnt)
// mstore(_tokens, cnt)
// mstore(_middlewareTimesIndexes, cnt)
// }

// completeQueuedWithdrawals(_queuedWithdrawals, _tokens, _middlewareTimesIndexes);
for (uint256 i = 0; i < withdrawalRoots.length; i++) {
withdrawalRootsSet.add(withdrawalRoots[i]);
}
return withdrawalRoots;
}

/// Advanced version
/// @notice Used to complete the specified `queuedWithdrawals`. The function caller must match `queuedWithdrawals[...].withdrawer`
/// @param _queuedWithdrawals The QueuedWithdrawals to complete.
/// @param _tokens Array of tokens for each QueuedWithdrawal. See `completeQueuedWithdrawal` for the usage of a single array.
/// @param _middlewareTimesIndexes One index to reference per QueuedWithdrawal. See `completeQueuedWithdrawal` for the usage of a single index.
/// @dev middlewareTimesIndex should be calculated off chain before calling this function by finding the first index that satisfies `slasher.canWithdraw`
function completeQueuedWithdrawals(IDelegationManager.Withdrawal[] memory _queuedWithdrawals, IERC20[][] memory _tokens, uint256[] memory _middlewareTimesIndexes) public onlyAdmin {
revert("FIX BELOW");
// uint256 num = _queuedWithdrawals.length;
// bool[] memory receiveAsTokens = new bool[](num);
// for (uint256 i = 0; i < num; i++) {
// bytes32 withdrawalRoot = eigenLayerDelegationManager.calculateWithdrawalRoot(_queuedWithdrawals[i]);
// emit CompletedQueuedWithdrawal(withdrawalRoot);

// /// so that the shares withdrawn from the specified strategies are sent to the caller
// receiveAsTokens[i] = true;
// withdrawalRootsSet.remove(withdrawalRoot);
// }
function completeQueuedWithdrawals(IDelegationManager.Withdrawal[] memory _queuedWithdrawals, IERC20[][] memory _tokens) external onlyAdmin {
uint256 num = _queuedWithdrawals.length;
bool[] memory receiveAsTokens = new bool[](num);
for (uint256 i = 0; i < num; i++) {
bytes32 withdrawalRoot = eigenLayerDelegationManager.calculateWithdrawalRoot(_queuedWithdrawals[i]);
emit CompletedQueuedWithdrawal(withdrawalRoot);

/// so that the shares withdrawn from the specified strategies are sent to the caller
receiveAsTokens[i] = true;
require(withdrawalRootsSet.remove(withdrawalRoot), "WITHDRAWAL_ROOT_NOT_FOUND");
}

// /// it will update the erc20 balances of this contract
// eigenLayerDelegationManager.completeQueuedWithdrawals(_queuedWithdrawals, _tokens, _middlewareTimesIndexes, receiveAsTokens);
/// it will update the erc20 balances of this contract
eigenLayerDelegationManager.completeQueuedWithdrawals(_queuedWithdrawals, _tokens, receiveAsTokens);
}

/// Enumerate the pending withdrawal roots
function pendingWithdrawalRoots() public view returns (bytes32[] memory) {
function pendingWithdrawalRoots() external view returns (bytes32[] memory) {
return withdrawalRootsSet.values();
}

Expand All @@ -293,11 +222,10 @@ contract EtherFiRestaker is Initializable, UUPSUpgradeable, OwnableUpgradeable,
return withdrawalRootsSet.contains(_withdrawalRoot);
}


// |--------------------------------------------------------------------------------------------|
// | VIEW functions |
// | VIEW functions |
// |--------------------------------------------------------------------------------------------|
function getTotalPooledEther() public view returns (uint256 total) {
function getTotalPooledEther() external view returns (uint256 total) {
total = address(this).balance + getTotalPooledEther(address(lido));
}

Expand All @@ -307,11 +235,18 @@ contract EtherFiRestaker is Initializable, UUPSUpgradeable, OwnableUpgradeable,
}

function getRestakedAmount(address _token) public view returns (uint256) {
revert("FIX BELOW");
// TokenInfo memory info = tokenInfos[_token];
// uint256 shares = eigenLayerStrategyManager.stakerStrategyShares(address(this), info.elStrategy);
// uint256 restaked = info.elStrategy.sharesToUnderlyingView(shares);
// return restaked;
TokenInfo memory info = tokenInfos[_token];
IStrategy[] memory strategies = new IStrategy[](1);
strategies[0] = info.elStrategy;

// get the shares locked in the EigenPod
// - `withdrawableShares` reflects the slashing on 'depositShares'
(uint256[] memory withdrawableShares, ) = eigenLayerDelegationManager.getWithdrawableShares(address(this), strategies);

// convert the share amount to the token's balance amount
uint256 restaked = info.elStrategy.sharesToUnderlyingView(withdrawableShares[0]);

return restaked;
}

function getEigenLayerRestakingStrategy(address _token) public view returns (IStrategy) {
Expand All @@ -326,21 +261,24 @@ contract EtherFiRestaker is Initializable, UUPSUpgradeable, OwnableUpgradeable,
TokenInfo memory info = tokenInfos[_token];
if (info.elStrategy != IStrategy(address(0))) {
uint256 restakedTokenAmount = getRestakedAmount(_token);
restaked = liquifier.quoteByFairValue(_token, restakedTokenAmount); /// restaked & pending for withdrawals
unrestaking = getEthAmountInEigenLayerPendingForWithdrawals(_token);
uint256 unrestakingTokenAmount = getAmountInEigenLayerPendingForWithdrawals(_token);
restaked = liquifier.quoteByFairValue(_token, restakedTokenAmount); // restaked & pending for withdrawals
unrestaking = liquifier.quoteByFairValue(_token, unrestakingTokenAmount); // restaked & pending for withdrawals
}
holding = liquifier.quoteByFairValue(_token, IERC20(_token).balanceOf(address(this))); /// eth value for erc20 holdings
pendingForWithdrawals = getEthAmountPendingForRedemption(_token);
pendingForWithdrawals = liquifier.quoteByFairValue(_token, getAmountPendingForRedemption(_token));
}

function getEthAmountInEigenLayerPendingForWithdrawals(address _token) public view returns (uint256) {
// get the amount of token restaked in EigenLayer pending for withdrawals
function getAmountInEigenLayerPendingForWithdrawals(address _token) public view returns (uint256) {
TokenInfo memory info = tokenInfos[_token];
if (info.elStrategy == IStrategy(address(0))) return 0;
uint256 amount = info.elStrategy.sharesToUnderlyingView(info.elSharesInPendingForWithdrawals);
return amount;
}

function getEthAmountPendingForRedemption(address _token) public view returns (uint256) {
// get the amount of token pending for redemption. e.g., pending in Lido's withdrawal queue
function getAmountPendingForRedemption(address _token) public view returns (uint256) {
uint256 total = 0;
if (_token == address(lido)) {
uint256[] memory stEthWithdrawalRequestIds = lidoWithdrawalQueue.getWithdrawalRequests(address(this));
Expand Down Expand Up @@ -373,22 +311,20 @@ contract EtherFiRestaker is Initializable, UUPSUpgradeable, OwnableUpgradeable,
}

// INTERNAL functions
function _queueWithdrawlsByShares(address token, uint256 shares) internal returns (bytes32[] memory) {
revert("FIX BELOW");
// IStrategy strategy = tokenInfos[token].elStrategy;
// IDelegationManager.QueuedWithdrawalParams[] memory params = new IDelegationManager.QueuedWithdrawalParams[](1);
// IStrategy[] memory strategies = new IStrategy[](1);
// strategies[0] = strategy;
// uint256[] memory sharesArr = new uint256[](1);
// sharesArr[0] = shares;

// params[0] = IDelegationManager.QueuedWithdrawalParams({
// strategies: strategies,
// shares: sharesArr,
// withdrawer: address(this)
// });
function _queueWithdrawalsByShares(address _token, uint256 _shares) internal returns (bytes32[] memory) {
IDelegationManagerTypes.QueuedWithdrawalParams[] memory params = new IDelegationManagerTypes.QueuedWithdrawalParams[](1);
IStrategy[] memory strategies = new IStrategy[](1);
uint256[] memory shares = new uint256[](1);

strategies[0] = tokenInfos[_token].elStrategy;
shares[0] = _shares;
params[0] = IDelegationManagerTypes.QueuedWithdrawalParams({
strategies: strategies,
depositShares: shares,
withdrawer: address(this)
});

// return queueWithdrawals(params);
return queueWithdrawalsWithParams(params);
}

function _min(uint256 _a, uint256 _b) internal pure returns (uint256) {
Expand All @@ -415,4 +351,4 @@ contract EtherFiRestaker is Initializable, UUPSUpgradeable, OwnableUpgradeable,
_requirePauser();
_;
}
}
}
Loading