Skip to content

Commit 938ea93

Browse files
committed
[M-01] + [M-02] + [M-04] + [I-03] + [I-05] audit fixes
1 parent 2d535a9 commit 938ea93

File tree

6 files changed

+104
-27
lines changed

6 files changed

+104
-27
lines changed

src/EtherFiNode.sol

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.27;
33

4+
import {IEtherFiNode} from "../src/interfaces/IEtherFiNode.sol";
5+
import {IEtherFiNodesManager} from "../src/interfaces/IEtherFiNodesManager.sol";
6+
import {IRoleRegistry} from "../src/interfaces/IRoleRegistry.sol";
7+
import {ILiquidityPool} from "../src/interfaces/ILiquidityPool.sol";
8+
49
import {IDelegationManager} from "../src/eigenlayer-interfaces/IDelegationManager.sol";
10+
import {IDelegationManagerTypes} from "../src/eigenlayer-interfaces/IDelegationManager.sol";
511
import {IEigenPodManager} from "../src/eigenlayer-interfaces/IEigenPodManager.sol";
612
import {IEigenPod} from "../src/eigenlayer-interfaces/IEigenPod.sol";
13+
import {IStrategy} from "../src/eigenlayer-interfaces/IStrategy.sol";
714
import {BeaconChainProofs} from "../src/eigenlayer-libraries/BeaconChainProofs.sol";
8-
import {IERC20} from "../lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol";
915

10-
import {IEtherFiNode} from "../src/interfaces/IEtherFiNode.sol";
11-
import {IEtherFiNodesManager} from "../src/interfaces/IEtherFiNodesManager.sol";
12-
import {IRoleRegistry} from "../src/interfaces/IRoleRegistry.sol";
13-
import {ILiquidityPool} from "../src/interfaces/ILiquidityPool.sol";
16+
import {IERC20} from "../lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol";
1417
import {LibCall} from "../lib/solady/src/utils/LibCall.sol";
1518

1619
contract EtherFiNode is IEtherFiNode {
@@ -48,6 +51,8 @@ contract EtherFiNode is IEtherFiNode {
4851
roleRegistry = IRoleRegistry(_roleRegistry);
4952
}
5053

54+
fallback() external payable {}
55+
5156
//--------------------------------------------------------------------------------------
5257
//---------------------------- Eigenlayer Interactions --------------------------------
5358
//--------------------------------------------------------------------------------------
@@ -80,32 +85,42 @@ contract EtherFiNode is IEtherFiNode {
8085
getEigenPod().verifyCheckpointProofs(balanceContainerProof, proofs);
8186
}
8287

83-
84-
/// @dev queue a withdrawal from eigenlayer. You must wait EIGENLAYER_WITHDRAWAL_DELAY_BLOCKS before claiming.
88+
/// @dev convenience function to queue a beaconETH withdrawal from eigenlayer. You must wait EIGENLAYER_WITHDRAWAL_DELAY_BLOCKS before claiming.
8589
/// It is fine to queue a withdrawal before validators have finished exiting on the beacon chain.
86-
function queueWithdrawal(IDelegationManager.QueuedWithdrawalParams calldata params) external onlyAdmin returns (bytes32 withdrawalRoot) {
90+
function queueETHWithdrawal(uint256 amount) external onlyAdmin returns (bytes32 withdrawalRoot) {
91+
92+
// beacon eth is always 1 to 1 with deposit shares
93+
uint256[] memory depositShares = new uint256[](1);
94+
depositShares[0] = amount;
95+
IStrategy[] memory strategies = new IStrategy[](1);
96+
strategies[0] = IStrategy(address(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0));
97+
98+
IDelegationManagerTypes.QueuedWithdrawalParams[] memory paramsArray = new IDelegationManagerTypes.QueuedWithdrawalParams[](1);
99+
paramsArray[0] = IDelegationManagerTypes.QueuedWithdrawalParams({
100+
strategies: strategies, // beacon eth
101+
depositShares: depositShares,
102+
__deprecated_withdrawer: address(this)
103+
});
87104

88-
// Implemented this way for convenience because we almost never queue multiple withdrawals at the same time
89-
IDelegationManager.QueuedWithdrawalParams[] memory paramsArray = new IDelegationManager.QueuedWithdrawalParams[](1);
90-
paramsArray[0] = params;
91105
return delegationManager.queueWithdrawals(paramsArray)[0];
92106
}
93107

94-
/// @dev completes all queued withdrawals that are currently claimable.
108+
109+
/// @dev completes all queued beaconETH withdrawals that are currently claimable.
95110
/// Note that since the node is usually delegated to an operator,
96111
/// most of the time this should be called with "receiveAsTokens" = true because
97112
/// receiving shares while delegated will simply redelegate the shares.
98-
function completeQueuedWithdrawals(bool receiveAsTokens) external onlyAdmin {
113+
function completeQueuedETHWithdrawals(bool receiveAsTokens) external onlyAdmin {
99114

100115
// because we are just dealing with beacon eth we don't need to populate the tokens[] array
101-
IERC20[] memory tokens;
116+
IERC20[] memory tokens = new IERC20[](1);
102117

103118
(IDelegationManager.Withdrawal[] memory queuedWithdrawals, ) = delegationManager.getQueuedWithdrawals(address(this));
104119
for (uint256 i = 0; i < queuedWithdrawals.length; i++) {
105120

106121
// skip this withdrawal if not enough time has passed
107122
uint32 slashableUntil = queuedWithdrawals[i].startBlock + EIGENLAYER_WITHDRAWAL_DELAY_BLOCKS;
108-
if (uint32(block.number) > slashableUntil) continue;
123+
if (uint32(block.number) < slashableUntil) continue;
109124

110125
delegationManager.completeQueuedWithdrawal(queuedWithdrawals[i], tokens, receiveAsTokens);
111126
}
@@ -117,9 +132,26 @@ contract EtherFiNode is IEtherFiNode {
117132
}
118133
}
119134

135+
/// @dev queue a withdrawal from eigenlayer. You must wait EIGENLAYER_WITHDRAWAL_DELAY_BLOCKS before claiming.
136+
/// For the general case of queuing a beaconETH withdrawal you can use queueETHWithdrawal instead.
137+
function queueWithdrawals(IDelegationManager.QueuedWithdrawalParams[] calldata params) external onlyAdmin returns (bytes32[] memory withdrawalRoots) {
138+
return delegationManager.queueWithdrawals(params);
139+
}
140+
141+
/// @dev complete an arbitrary withdrawal from eigenlayer.
142+
/// For the general case of claiming beaconETH withdrawals you can use completeQueuedETHWithdrawals instead.
143+
function completeQueuedWithdrawals(
144+
IDelegationManager.Withdrawal[] calldata withdrawals,
145+
IERC20[][] calldata tokens,
146+
bool[] calldata receiveAsTokens
147+
) external onlyAdmin {
148+
delegationManager.completeQueuedWithdrawals(withdrawals, tokens, receiveAsTokens);
149+
}
150+
151+
120152
// @notice transfers any funds held by the node to the liquidity pool.
121153
// @dev under normal operations it is not expected for eth to accumulate in the nodes,
122-
// this is just to handle any exceptional cases such as someone sending directly to the node.
154+
// this is just to handle any exceptional cases such as someone sending directly to the node.
123155
function sweepFunds() external onlyAdmin {
124156
(bool sent, ) = payable(address(liquidityPool)).call{value: address(this).balance, gas: 20000}("");
125157
if (!sent) revert TransferFailed();

src/EtherFiNodesManager.sol

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,24 @@ contract EtherFiNodesManager is
9191
IEtherFiNode(etherfiNodeAddress(id)).setProofSubmitter(proofSubmitter);
9292
}
9393

94-
function queueWithdrawal(uint256 id, IDelegationManager.QueuedWithdrawalParams calldata params) external onlyCallForwarder returns (bytes32 withdrawalRoot) {
95-
return IEtherFiNode(etherfiNodeAddress(id)).queueWithdrawal(params);
94+
function queueETHWithdrawal(uint256 id, uint256 amount) external onlyCallForwarder returns (bytes32 withdrawalRoot) {
95+
return IEtherFiNode(etherfiNodeAddress(id)).queueETHWithdrawal(amount);
9696
}
9797

98-
function completeQueuedWithdrawals(uint256 id, bool receiveAsTokens) external onlyCallForwarder {
99-
IEtherFiNode(etherfiNodeAddress(id)).completeQueuedWithdrawals(receiveAsTokens);
98+
function completeQueuedETHWithdrawals(uint256 id, bool receiveAsTokens) external onlyCallForwarder {
99+
IEtherFiNode(etherfiNodeAddress(id)).completeQueuedETHWithdrawals(receiveAsTokens);
100+
}
101+
102+
function queueWithdrawals(uint256 id, IDelegationManager.QueuedWithdrawalParams[] calldata params) external onlyCallForwarder {
103+
IEtherFiNode(etherfiNodeAddress(id)).queueWithdrawals(params);
104+
}
105+
106+
function completeQueuedWithdrawal(uint256 id, IDelegationManager.Withdrawal[] calldata withdrawals, IERC20[][] calldata tokens, bool[] calldata receiveAsTokens) external onlyCallForwarder {
107+
IEtherFiNode(etherfiNodeAddress(id)).completeQueuedWithdrawals(withdrawals, tokens, receiveAsTokens);
108+
}
109+
110+
function sweepFunds(uint256 id) external onlyCallForwarder {
111+
IEtherFiNode(etherfiNodeAddress(id)).sweepFunds;
100112
}
101113

102114
//-------------------------------------------------------------------

src/interfaces/IEtherFiNode.sol

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@ interface IEtherFiNode {
1212
function createEigenPod() external returns (address);
1313
function getEigenPod() external view returns (IEigenPod);
1414
function startCheckpoint() external;
15-
function setProofSubmitter(address _newProofSubmitter) external;
16-
function queueWithdrawal(IDelegationManager.QueuedWithdrawalParams calldata params) external returns (bytes32 withdrawalRoot);
17-
function completeQueuedWithdrawals(bool receiveAsTokens) external;
15+
function setProofSubmitter(address newProofSubmitter) external;
1816
function verifyCheckpointProofs(BeaconChainProofs.BalanceContainerProof calldata balanceContainerProof, BeaconChainProofs.BalanceProof[] calldata proofs) external;
17+
function queueETHWithdrawal(uint256 amount) external returns (bytes32 withdrawalRoot);
18+
function completeQueuedETHWithdrawals(bool receiveAsTokens) external ;
19+
function queueWithdrawals(IDelegationManager.QueuedWithdrawalParams[] calldata params) external returns (bytes32[] memory withdrawalRoot);
20+
function completeQueuedWithdrawals(IDelegationManager.Withdrawal[] calldata withdrawals, IERC20[][] calldata tokens, bool[] calldata receiveAsTokens) external;
21+
function sweepFunds() external;
1922

2023
// call forwarding
2124
function forwardEigenPodCall(bytes memory data) external returns (bytes memory);

src/interfaces/IEtherFiNodesManager.sol

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@ interface IEtherFiNodesManager {
2020
function createEigenPod(uint256 id) external returns (address);
2121
function startCheckpoint(uint256 id) external;
2222
function verifyCheckpointProofs(uint256 id, BeaconChainProofs.BalanceContainerProof calldata balanceContainerProof, BeaconChainProofs.BalanceProof[] calldata proofs) external;
23-
function setProofSubmitter(uint256 id, address _newProofSubmitter) external;
24-
function queueWithdrawal(uint256 id, IDelegationManager.QueuedWithdrawalParams calldata params) external returns (bytes32 withdrawalRoot);
25-
function completeQueuedWithdrawals(uint256 id, bool receiveAsTokens) external;
23+
function setProofSubmitter(uint256 id, address newProofSubmitter) external;
24+
function queueETHWithdrawal(uint256 id, uint256 amount) external returns (bytes32 withdrawalRoot);
25+
function completeQueuedETHWithdrawals(uint256 id, bool receiveAsTokens) external;
26+
function queueWithdrawals(uint256 id, IDelegationManager.QueuedWithdrawalParams[] calldata params) external;
27+
function completeQueuedWithdrawal(uint256 id, IDelegationManager.Withdrawal[] calldata withdrawals, IERC20[][] calldata tokens, bool[] calldata receiveAsTokens) external;
28+
function sweepFunds(uint256 id) external;
2629

2730
// call forwarding
2831
function updateAllowedForwardedExternalCalls(bytes4 selector, address target, bool allowed) external;

test/common/ArrayTestHelper.sol

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,16 @@ contract ArrayTestHelper {
2626
vals[0] = val;
2727
return vals;
2828
}
29+
function toArray_bytes(bytes memory val) public pure returns (bytes[] memory) {
30+
bytes[] memory vals = new bytes[](1);
31+
vals[0] = val;
32+
return vals;
33+
}
2934
function toArray(IDelegationManager.Withdrawal memory withdrawal) public pure returns (IDelegationManager.Withdrawal[] memory) {
3035
IDelegationManager.Withdrawal[] memory vals = new IDelegationManager.Withdrawal[](1);
3136
vals[0] = withdrawal;
3237
return vals;
3338
}
34-
3539
function toArray(IStakingManager.DepositData memory deposit) public pure returns (IStakingManager.DepositData[] memory) {
3640
IStakingManager.DepositData[] memory vals = new IStakingManager.DepositData[](1);
3741
vals[0] = deposit;

test/prelude.t.sol

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ contract PreludeTest is Test, ArrayTestHelper {
2828
NodeOperatorManager nodeOperatorManager = NodeOperatorManager(0xd5edf7730ABAd812247F6F54D7bd31a52554e35E);
2929

3030
address admin = vm.addr(0x9876543210);
31+
address forwarder = vm.addr(0x1234567890);
3132
address stakingDepositContract = address(0x00000000219ab540356cBB839Cbe05303d7705Fa);
3233
address eigenPodManager = address(0x91E677b07F7AF907ec9a428aafA9fc14a0d3A338);
3334
address delegationManager = address(0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A);
@@ -79,6 +80,7 @@ contract PreludeTest is Test, ArrayTestHelper {
7980
roleRegistry.grantRole(etherFiNodeImpl.ETHERFI_NODE_ADMIN_ROLE(), address(etherFiNodesManager));
8081
roleRegistry.grantRole(etherFiNodeImpl.ETHERFI_NODE_ADMIN_ROLE(), address(stakingManager));
8182
roleRegistry.grantRole(etherFiNodesManager.ETHERFI_NODES_MANAGER_ADMIN_ROLE(), admin);
83+
roleRegistry.grantRole(etherFiNodesManager.ETHERFI_NODES_MANAGER_CALL_FORWARDER_ROLE(), forwarder);
8284
roleRegistry.grantRole(stakingManager.STAKING_MANAGER_NODE_CREATOR_ROLE(), admin);
8385
vm.stopPrank();
8486

@@ -163,6 +165,27 @@ contract PreludeTest is Test, ArrayTestHelper {
163165

164166
vm.prank(address(liquidityPool));
165167
stakingManager.confirmAndFundBeaconValidators{value: confirmAmount}(toArray(confirmDepositData), validatorSize);
168+
}
169+
170+
function test_withdrawRestakedValidatorETH() public {
171+
172+
bytes memory validatorPubkey = hex"892c95f4e93ab042ee39397bff22cc43298ff4b2d6d6dec3f28b8b8ebcb5c65ab5e6fc29301c1faee473ec095f9e4306";
173+
bytes32 pubkeyHash = etherFiNodesManager.calculateValidatorPubkeyHash(validatorPubkey);
174+
uint256 legacyID = 10885;
175+
176+
// force link this validator
177+
vm.prank(admin);
178+
etherFiNodesManager.linkLegacyValidatorIds(toArray_u256(legacyID), toArray_bytes(validatorPubkey));
179+
180+
vm.prank(forwarder);
181+
etherFiNodesManager.queueETHWithdrawal(uint256(pubkeyHash), 1 ether);
182+
183+
// poke some withdrawable funds into the restakedExecutionLayerGwei storage slot of the eigenpod
184+
address eigenpod = etherFiNodesManager.getEigenPod(uint256(pubkeyHash));
185+
vm.store(eigenpod, bytes32(uint256(52)) /*slot*/, bytes32(uint256(50 ether / 1 gwei)));
166186

187+
vm.roll(block.number + (7200 * 15));
188+
vm.prank(forwarder);
189+
etherFiNodesManager.completeQueuedETHWithdrawals(uint256(pubkeyHash), true);
167190
}
168191
}

0 commit comments

Comments
 (0)