-
Notifications
You must be signed in to change notification settings - Fork 195
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
[VAULTS] Simplify beacon chain deposit processing #925
Merged
+228
−189
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
cfef66c
feat(StakingVault): simplify deposit process
failingtwice da66162
fix: deposit data root calculation
failingtwice d4a234f
feat(StakingVault): update year
failingtwice 33eb2d7
fix: linting
failingtwice 8fa9089
feat(StakingVault): include total deposit amount in the event
failingtwice 42fd97b
feat(StakingVault): deposit data root util
failingtwice 71e93b2
feat(StakingVault): deposit data root util
failingtwice 95b85fc
merge
failingtwice 04d69df
Merge branch 'feat/vaults' of https://github.com/lidofinance/core int…
failingtwice b2e6660
fix(IDepositContract): update year
failingtwice 142ea4d
fix: formatting
failingtwice b12a322
test: add sample source
failingtwice File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> | ||
// SPDX-License-Identifier: GPL-3.0 | ||
|
||
// See contracts/COMPILERS.md | ||
pragma solidity 0.8.25; | ||
|
||
interface IDepositContract { | ||
function get_deposit_root() external view returns (bytes32 rootHash); | ||
|
||
function deposit( | ||
bytes calldata pubkey, // 48 bytes | ||
bytes calldata withdrawal_credentials, // 32 bytes | ||
bytes calldata signature, // 96 bytes | ||
bytes32 deposit_data_root | ||
) external payable; | ||
} |
114 changes: 0 additions & 114 deletions
114
contracts/0.8.25/vaults/BeaconChainDepositLogistics.sol
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,14 @@ | ||
// SPDX-FileCopyrightText: 2024 Lido <[email protected]> | ||
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> | ||
// SPDX-License-Identifier: GPL-3.0 | ||
|
||
// See contracts/COMPILERS.md | ||
pragma solidity 0.8.25; | ||
|
||
import {OwnableUpgradeable} from "contracts/openzeppelin/5.2/upgradeable/access/OwnableUpgradeable.sol"; | ||
import {BeaconChainDepositLogistics} from "./BeaconChainDepositLogistics.sol"; | ||
|
||
import {VaultHub} from "./VaultHub.sol"; | ||
|
||
import {IDepositContract} from "../interfaces/IDepositContract.sol"; | ||
import {IStakingVault} from "./interfaces/IStakingVault.sol"; | ||
|
||
/** | ||
|
@@ -49,7 +50,7 @@ import {IStakingVault} from "./interfaces/IStakingVault.sol"; | |
* deposit contract. | ||
* | ||
*/ | ||
contract StakingVault is IStakingVault, BeaconChainDepositLogistics, OwnableUpgradeable { | ||
contract StakingVault is IStakingVault, OwnableUpgradeable { | ||
/** | ||
* @notice ERC-7201 storage namespace for the vault | ||
* @dev ERC-7201 namespace is used to prevent upgrade collisions | ||
|
@@ -77,6 +78,12 @@ contract StakingVault is IStakingVault, BeaconChainDepositLogistics, OwnableUpgr | |
*/ | ||
VaultHub private immutable VAULT_HUB; | ||
|
||
/** | ||
* @notice Address of `BeaconChainDepositContract` | ||
* Set immutably in the constructor to avoid storage costs | ||
*/ | ||
IDepositContract private immutable BEACON_CHAIN_DEPOSIT_CONTRACT; | ||
|
||
/** | ||
* @notice Storage offset slot for ERC-7201 namespace | ||
* The storage namespace is used to prevent upgrade collisions | ||
|
@@ -91,13 +98,12 @@ contract StakingVault is IStakingVault, BeaconChainDepositLogistics, OwnableUpgr | |
* @param _beaconChainDepositContract Address of `BeaconChainDepositContract` | ||
* @dev Fixes `VaultHub` and `BeaconChainDepositContract` addresses in the bytecode of the implementation | ||
*/ | ||
constructor( | ||
address _vaultHub, | ||
address _beaconChainDepositContract | ||
) BeaconChainDepositLogistics(_beaconChainDepositContract) { | ||
constructor(address _vaultHub, address _beaconChainDepositContract) { | ||
if (_vaultHub == address(0)) revert ZeroArgument("_vaultHub"); | ||
if (_beaconChainDepositContract == address(0)) revert ZeroArgument("_beaconChainDepositContract"); | ||
|
||
VAULT_HUB = VaultHub(_vaultHub); | ||
BEACON_CHAIN_DEPOSIT_CONTRACT = IDepositContract(_beaconChainDepositContract); | ||
|
||
// Prevents reinitialization of the implementation | ||
_disableInitializers(); | ||
|
@@ -109,7 +115,7 @@ contract StakingVault is IStakingVault, BeaconChainDepositLogistics, OwnableUpgr | |
* @param _nodeOperator Address of the node operator | ||
* @param - Additional initialization parameters | ||
*/ | ||
function initialize(address _owner, address _nodeOperator, bytes calldata /* _params */ ) external initializer { | ||
function initialize(address _owner, address _nodeOperator, bytes calldata /* _params */) external initializer { | ||
__Ownable_init(_owner); | ||
_getStorage().nodeOperator = _nodeOperator; | ||
} | ||
|
@@ -142,6 +148,14 @@ contract StakingVault is IStakingVault, BeaconChainDepositLogistics, OwnableUpgr | |
return address(VAULT_HUB); | ||
} | ||
|
||
/** | ||
* @notice Returns the address of `BeaconChainDepositContract` | ||
* @return Address of `BeaconChainDepositContract` | ||
*/ | ||
function depositContract() external view returns (address) { | ||
return address(BEACON_CHAIN_DEPOSIT_CONTRACT); | ||
} | ||
|
||
/** | ||
* @notice Returns the total valuation of `StakingVault` | ||
* @return Total valuation in ether | ||
|
@@ -285,22 +299,28 @@ contract StakingVault is IStakingVault, BeaconChainDepositLogistics, OwnableUpgr | |
|
||
/** | ||
* @notice Performs a deposit to the beacon chain deposit contract | ||
* @param _numberOfDeposits Number of deposits to make | ||
* @param _pubkeys Concatenated validator public keys | ||
* @param _signatures Concatenated deposit data signatures | ||
* @param _deposits Array of deposit structs | ||
* @dev Includes a check to ensure StakingVault is balanced before making deposits | ||
*/ | ||
function depositToBeaconChain( | ||
uint256 _numberOfDeposits, | ||
bytes calldata _pubkeys, | ||
bytes calldata _signatures | ||
) external { | ||
if (_numberOfDeposits == 0) revert ZeroArgument("_numberOfDeposits"); | ||
if (!isBalanced()) revert Unbalanced(); | ||
function depositToBeaconChain(Deposit[] calldata _deposits) external { | ||
if (_deposits.length == 0) revert ZeroArgument("_deposits"); | ||
if (msg.sender != _getStorage().nodeOperator) revert NotAuthorized("depositToBeaconChain", msg.sender); | ||
if (!isBalanced()) revert Unbalanced(); | ||
|
||
uint256 totalAmount = 0; | ||
uint256 numberOfDeposits = _deposits.length; | ||
for (uint256 i = 0; i < numberOfDeposits; i++) { | ||
Deposit calldata deposit = _deposits[i]; | ||
BEACON_CHAIN_DEPOSIT_CONTRACT.deposit{value: deposit.amount}( | ||
deposit.pubkey, | ||
bytes.concat(withdrawalCredentials()), | ||
deposit.signature, | ||
deposit.depositDataRoot | ||
); | ||
totalAmount += deposit.amount; | ||
} | ||
|
||
_makeBeaconChainDeposits32ETH(_numberOfDeposits, bytes.concat(withdrawalCredentials()), _pubkeys, _signatures); | ||
emit DepositedToBeaconChain(msg.sender, _numberOfDeposits, _numberOfDeposits * 32 ether); | ||
emit DepositedToBeaconChain(msg.sender, numberOfDeposits, totalAmount); | ||
} | ||
|
||
/** | ||
|
@@ -370,6 +390,57 @@ contract StakingVault is IStakingVault, BeaconChainDepositLogistics, OwnableUpgr | |
emit Reported(_valuation, _inOutDelta, _locked); | ||
} | ||
|
||
/** | ||
* @notice Computes the deposit data root for a validator deposit | ||
* @param _pubkey Validator public key, 48 bytes | ||
* @param _withdrawalCredentials Withdrawal credentials, 32 bytes | ||
* @param _signature Signature of the deposit, 96 bytes | ||
* @param _amount Amount of ether to deposit, in wei | ||
* @return Deposit data root as bytes32 | ||
* @dev This function computes the deposit data root according to the deposit contract's specification. | ||
* The deposit data root is check upon deposit to the deposit contract as a protection against malformed deposit data. | ||
* See more: https://etherscan.io/address/0x00000000219ab540356cbb839cbe05303d7705fa#code | ||
* | ||
*/ | ||
function computeDepositDataRoot( | ||
bytes calldata _pubkey, | ||
bytes calldata _withdrawalCredentials, | ||
bytes calldata _signature, | ||
uint256 _amount | ||
) external view returns (bytes32) { | ||
// Step 1. Convert the deposit amount in wei to gwei in 64-bit bytes | ||
bytes memory amountBE64 = abi.encodePacked(uint64(_amount / 1 gwei)); | ||
|
||
// Step 2. Convert the amount to little-endian format by flipping the bytes 🧠 | ||
bytes memory amountLE64 = new bytes(8); | ||
amountLE64[0] = amountBE64[7]; | ||
amountLE64[1] = amountBE64[6]; | ||
amountLE64[2] = amountBE64[5]; | ||
amountLE64[3] = amountBE64[4]; | ||
amountLE64[4] = amountBE64[3]; | ||
amountLE64[5] = amountBE64[2]; | ||
amountLE64[6] = amountBE64[1]; | ||
amountLE64[7] = amountBE64[0]; | ||
|
||
// Step 3. Compute the root of the pubkey | ||
bytes32 pubkeyRoot = sha256(abi.encodePacked(_pubkey, bytes16(0))); | ||
|
||
// Step 4. Compute the root of the signature | ||
bytes32 sigSlice1Root = sha256(abi.encodePacked(_signature[0:64])); | ||
bytes32 sigSlice2Root = sha256(abi.encodePacked(_signature[64:], bytes32(0))); | ||
bytes32 signatureRoot = sha256(abi.encodePacked(sigSlice1Root, sigSlice2Root)); | ||
|
||
// Step 5. Compute the root-toot-toorootoo of the deposit data | ||
bytes32 depositDataRoot = sha256( | ||
abi.encodePacked( | ||
sha256(abi.encodePacked(pubkeyRoot, _withdrawalCredentials)), | ||
sha256(abi.encodePacked(amountLE64, bytes24(0), signatureRoot)) | ||
) | ||
); | ||
|
||
return depositDataRoot; | ||
} | ||
|
||
function _getStorage() private pure returns (ERC7201Storage storage $) { | ||
assembly { | ||
$.slot := ERC721_STORAGE_LOCATION | ||
|
@@ -397,9 +468,8 @@ contract StakingVault is IStakingVault, BeaconChainDepositLogistics, OwnableUpgr | |
* @notice Emitted when ether is deposited to `DepositContract` | ||
* @param sender Address that initiated the deposit | ||
* @param deposits Number of validator deposits made | ||
* @param amount Total amount of ether deposited | ||
*/ | ||
event DepositedToBeaconChain(address indexed sender, uint256 deposits, uint256 amount); | ||
event DepositedToBeaconChain(address indexed sender, uint256 deposits, uint256 totalAmount); | ||
|
||
/** | ||
* @notice Emitted when a validator exit request is made | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Submodule forge-std
updated
25 files
+1 −1 | README.md | |
+1 −1 | package.json | |
+1 −14 | src/StdChains.sol | |
+3 −3 | src/StdCheats.sol | |
+0 −9 | src/StdInvariant.sol | |
+0 −104 | src/StdJson.sol | |
+1 −1 | src/StdStorage.sol | |
+0 −104 | src/StdToml.sol | |
+70 −290 | src/Vm.sol | |
+608 −635 | src/console.sol | |
+1,555 −1 | src/console2.sol | |
+2 −2 | src/interfaces/IERC4626.sol | |
+5 −1 | src/mocks/MockERC721.sol | |
+4 −693 | src/safeconsole.sol | |
+1 −1 | test/StdAssertions.t.sol | |
+12 −19 | test/StdChains.t.sol | |
+10 −10 | test/StdCheats.t.sol | |
+12 −12 | test/StdError.t.sol | |
+1 −1 | test/StdJson.t.sol | |
+14 −4 | test/StdMath.t.sol | |
+5 −13 | test/StdStorage.t.sol | |
+1 −1 | test/StdStyle.t.sol | |
+1 −1 | test/StdToml.t.sol | |
+12 −12 | test/StdUtils.t.sol | |
+6 −9 | test/Vm.t.sol |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.