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

[VAULTS] Simplify beacon chain deposit processing #925

Merged
merged 12 commits into from
Jan 22, 2025
16 changes: 16 additions & 0 deletions contracts/0.8.25/interfaces/IDepositContract.sol
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 contracts/0.8.25/vaults/BeaconChainDepositLogistics.sol

This file was deleted.

14 changes: 0 additions & 14 deletions contracts/0.8.25/vaults/Dashboard.sol
Original file line number Diff line number Diff line change
Expand Up @@ -460,20 +460,6 @@ contract Dashboard is AccessControlEnumerable {
stakingVault().requestValidatorExit(_validatorPublicKey);
}

/**
* @dev Deposits validators to the beacon chain
* @param _numberOfDeposits Number of validator deposits
* @param _pubkeys Concatenated public keys of the validators
* @param _signatures Concatenated signatures of the validators
*/
function _depositToBeaconChain(
uint256 _numberOfDeposits,
bytes calldata _pubkeys,
bytes calldata _signatures
) internal {
stakingVault().depositToBeaconChain(_numberOfDeposits, _pubkeys, _signatures);
}

/**
* @dev Mints stETH tokens backed by the vault to a recipient
* @param _recipient Address of the recipient
Expand Down
114 changes: 92 additions & 22 deletions contracts/0.8.25/vaults/StakingVault.sol
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";

/**
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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();
Expand All @@ -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;
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -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(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Step 5. Compute the root-toot-toorootoo of the deposit data
// Step 5. Compute the root-toot-totoro of the deposit data

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
Expand Down Expand Up @@ -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
Expand Down
14 changes: 9 additions & 5 deletions contracts/0.8.25/vaults/interfaces/IStakingVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,18 @@ interface IStakingVault {
int128 inOutDelta;
}

struct Deposit {
bytes pubkey;
bytes signature;
uint256 amount;
bytes32 depositDataRoot;
failingtwice marked this conversation as resolved.
Show resolved Hide resolved
}

function initialize(address _owner, address _operator, bytes calldata _params) external;
function version() external pure returns(uint64);
function getInitializedVersion() external view returns (uint64);
function vaultHub() external view returns (address);
function depositContract() external view returns (address);
function nodeOperator() external view returns (address);
function locked() external view returns (uint256);
function valuation() external view returns (uint256);
Expand All @@ -33,11 +41,7 @@ interface IStakingVault {
function withdrawalCredentials() external view returns (bytes32);
function fund() external payable;
function withdraw(address _recipient, uint256 _ether) external;
function depositToBeaconChain(
uint256 _numberOfDeposits,
bytes calldata _pubkeys,
bytes calldata _signatures
) external;
function depositToBeaconChain(Deposit[] calldata _deposits) external;
function requestValidatorExit(bytes calldata _pubkeys) external;
function lock(uint256 _locked) external;
function rebalance(uint256 _ether) external;
Expand Down
Loading
Loading