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] #217

Open
wants to merge 32 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6d815f0
set claimer
jtfirek Dec 19, 2024
27e3df5
Merge branch 'master' into set-claimer-for-restaker
jtfirek Dec 23, 2024
2175295
initial repo setup for EL slashing upgrade
seongyun-ko Dec 24, 2024
afe325b
Merge branch 'master' into el_slashing
seongyun-ko Dec 24, 2024
9402d38
fix foundry.toml
seongyun-ko Dec 24, 2024
10eb7a3
update {EtherFiNode, EtherFiNodesManager} for slashing, mark 'FIX BEL…
seongyun-ko Dec 24, 2024
28356c4
update etherfi viewer for slashing changes
solipsis Dec 27, 2024
6fbe2c4
more view functions
solipsis Dec 27, 2024
240cda5
Merge pull request #221 from etherfi-protocol/syko/el_slashing/etherf…
seongyun-ko Dec 30, 2024
92d8bf0
(1) update EtherFiRestaker, (2) fixes in EtherFiNode
seongyun-ko Dec 30, 2024
7d51cf1
fix 'getEthAmountInEigenLayerPendingForWithdrawals' to quote the toke…
seongyun-ko Dec 30, 2024
b3f608c
remove 'getEthAmountInEigenLayerPendingForWithdrawals()'
seongyun-ko Dec 30, 2024
563d052
use 'quoteByFairValue' where needed, improve function naming
seongyun-ko Dec 30, 2024
974b5b9
Merge branch 'syko/feature/el_slashing/ether_fi_restaker' into set-cl…
seongyun-ko Dec 31, 2024
ba14dbb
Merge pull request #215 from etherfi-protocol/set-claimer-for-restaker
seongyun-ko Dec 31, 2024
2d02ca2
remove 'test_ForcedPartialWithdrawal_succeeds'
seongyun-ko Dec 31, 2024
ca05567
remove unnecessary tests, removed unnecessary mainent fork
seongyun-ko Dec 31, 2024
b2157a5
add the deprecated storage variable back and fix typo
seongyun-ko Dec 31, 2024
e9fb045
Merge pull request #222 from etherfi-protocol/syko/feature/el_slashin…
solipsis Dec 31, 2024
d066601
Merge pull request #218 from etherfi-protocol/syko/feature/el_slashin…
solipsis Dec 31, 2024
1f9da93
update forge version for features used by eigenlayer contracts
solipsis Jan 3, 2025
79aa199
overiddable mocks for all core eigenlayer contracts used in slashing
solipsis Jan 3, 2025
3c0a7af
array helper library, more wiring up of mocks
solipsis Jan 4, 2025
c6acd33
processNodeExit mock succeeds
solipsis Jan 4, 2025
b67a0ce
tests for withdrawal flows after slashing upgrade
solipsis Jan 6, 2025
a1c4c35
helper cleanup
solipsis Jan 6, 2025
51a3374
missing file
solipsis Jan 6, 2025
7d47b5a
additional test for slashing when not last validator
solipsis Jan 8, 2025
146c183
Merge pull request #225 from etherfi-protocol/el_slashing_mock_testing
solipsis Jan 8, 2025
071af4e
pull the updates on the interfaces
seongyun-ko Jan 9, 2025
28e3ac8
update mocks for new interfaces
solipsis Jan 9, 2025
59b55b3
Merge pull request #226 from etherfi-protocol/syko/feature/update_el_…
seongyun-ko Jan 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ gas_reports = ["*"]
optimizer_runs = 2000
extra_output = ["storageLayout"]
bytecode_hash = 'none'
solc-version = '0.8.24'
bytecode_hash = 'none'
solc-version = '0.8.27'

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
[rpc_endpoints]
Expand Down
5 changes: 2 additions & 3 deletions script/deploys/DeployEtherFiViewer.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ contract DeployEtherFiViewer is Script {
assert(etherFiNodeAddresses[0] == 0x31db9021ec8E1065e1f55553c69e1B1ea9d20533);
assert(etherFiNodeAddresses[1] == 0xC3D3662A44c0d80080D3AF0eea752369c504724e);

viewer.EigenPod_mostRecentWithdrawalTimestamp(validatorIds);
viewer.EigenPod_hasRestaked(validatorIds);

}
}
}
184 changes: 74 additions & 110 deletions src/EtherFiNode.sol

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/EtherFiNodesManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -217,10 +217,10 @@ contract EtherFiNodesManager is
}
}

function completeQueuedWithdrawals(uint256[] calldata _validatorIds, IDelegationManager.Withdrawal[] memory withdrawals, uint256[] calldata middlewareTimesIndexes, bool _receiveAsTokens) external onlyOperatingAdmin {
function completeQueuedWithdrawals(uint256[] calldata _validatorIds, IDelegationManager.Withdrawal[] memory withdrawals, bool _receiveAsTokens) external onlyOperatingAdmin {
for (uint256 i = 0; i < _validatorIds.length; i++) {
address etherfiNode = etherfiNodeAddress[_validatorIds[i]];
IEtherFiNode(etherfiNode).completeQueuedWithdrawal(withdrawals[i], middlewareTimesIndexes[i], _receiveAsTokens);
IEtherFiNode(etherfiNode).completeQueuedWithdrawal(withdrawals[i], _receiveAsTokens);
}
}

Expand Down
163 changes: 53 additions & 110 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,16 +154,12 @@ contract EtherFiRestaker is Initializable, UUPSUpgradeable, OwnableUpgradeable,

// undelegate from the current AVS operator & un-restake all
function undelegate() external onlyAdmin returns (bytes32[] memory) {
// 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));
assert(withdrawalRoots.length == 0);

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

return withdrawalRoots;
}

Expand All @@ -174,95 +179,24 @@ 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) {
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];
}
function queueWithdrawalsWithParams(IDelegationManagerTypes.QueuedWithdrawalParams[] memory params) public onlyAdmin returns (bytes32[] memory) {
bytes32[] memory withdrawalRoots = eigenLayerDelegationManager.queueWithdrawals(params);

withdrawalRootToWithdrawal[withdrawalRoots[i]] = withdrawals[i];
for (uint256 i = 0; i < withdrawalRoots.length; 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 {
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);

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);
}

/// 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 {
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++) {
Expand All @@ -271,15 +205,15 @@ contract EtherFiRestaker is Initializable, UUPSUpgradeable, OwnableUpgradeable,

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

/// it will update the erc20 balances of this contract
eigenLayerDelegationManager.completeQueuedWithdrawals(_queuedWithdrawals, _tokens, _middlewareTimesIndexes, receiveAsTokens);
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 @@ -288,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 @@ -303,8 +236,16 @@ contract EtherFiRestaker is Initializable, UUPSUpgradeable, OwnableUpgradeable,

function getRestakedAmount(address _token) public view returns (uint256) {
TokenInfo memory info = tokenInfos[_token];
uint256 shares = eigenLayerStrategyManager.stakerStrategyShares(address(this), info.elStrategy);
uint256 restaked = info.elStrategy.sharesToUnderlyingView(shares);
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;
}

Expand All @@ -320,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 @@ -367,21 +311,20 @@ contract EtherFiRestaker is Initializable, UUPSUpgradeable, OwnableUpgradeable,
}

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

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

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

function _min(uint256 _a, uint256 _b) internal pure returns (uint256) {
Expand All @@ -408,4 +351,4 @@ contract EtherFiRestaker is Initializable, UUPSUpgradeable, OwnableUpgradeable,
_requirePauser();
_;
}
}
}
2 changes: 0 additions & 2 deletions src/Liquifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,6 @@ contract Liquifier is Initializable, UUPSUpgradeable, OwnableUpgradeable, Pausab
address public etherfiRestaker;

event Liquified(address _user, uint256 _toEEthAmount, address _fromToken, bool _isRestaked);
event RegisteredQueuedWithdrawal(bytes32 _withdrawalRoot, IStrategyManager.DeprecatedStruct_QueuedWithdrawal _queuedWithdrawal);
event RegisteredQueuedWithdrawal_V2(bytes32 _withdrawalRoot, IDelegationManager.Withdrawal _queuedWithdrawal);
event CompletedQueuedWithdrawal(bytes32 _withdrawalRoot);
event QueuedStEthWithdrawals(uint256[] _reqIds);
event CompletedStEthQueuedWithdrawals(uint256[] _reqIds);
Expand Down
Loading
Loading