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] PredepositGuarantee #932

Open
wants to merge 66 commits into
base: feat/vaults
Choose a base branch
from
Open

Conversation

failingtwice
Copy link
Contributor

@failingtwice failingtwice commented Jan 28, 2025

This pull request introduces PredepositGuarantee contract that acts as deposit security layer for all Lido connected vaults.

@tamtamchik tamtamchik added solidity issues/tasks related to smart contract code vaults labels Jan 29, 2025
Copy link
Contributor

@TheDZhon TheDZhon left a comment

Choose a reason for hiding this comment

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

👀

@@ -68,6 +68,7 @@ contract StakingVault is IStakingVault, OwnableUpgradeable {
uint128 locked;
int128 inOutDelta;
address nodeOperator;
// depositGuardian becomes the depositor, instead of just guardian, perhaps a renaming is needed 🌚
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe make depositGuardian immutable

Copy link
Contributor

Choose a reason for hiding this comment

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

Changable but only for unconnceted vaults


// TODO: think about naming. It's not a deposit guardian, it's the depositor itself
// TODO: minor UX improvement: perhaps there's way to reuse predeposits for a different validator without withdrawing
contract PredepositGuardian {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
contract PredepositGuardian {
contract PredepositGuarantee {

Copy link
Contributor

Choose a reason for hiding this comment

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

fixed

Comment on lines 14 to 15
mapping(bytes32 validatorId => bool isPreDeposited) public validatorPredeposits;
mapping(bytes32 validatorId => bytes32 withdrawalCredentials) public validatorWithdrawalCredentials;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
mapping(bytes32 validatorId => bool isPreDeposited) public validatorPredeposits;
mapping(bytes32 validatorId => bytes32 withdrawalCredentials) public validatorWithdrawalCredentials;
mapping(address nodeOperator => uint256) public nodeOperatorGuarantee;
mapping(bytes validatorPubkey => bytes32 withdrawalCredentials) public validatorWithdrawalCredentials;

Copy link
Contributor

Choose a reason for hiding this comment

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

fixed

mapping(bytes32 validatorId => bytes32 withdrawalCredentials) public validatorWithdrawalCredentials;

// Question: predeposit is permissionless, i.e. the msg.sender doesn't have to be the node operator,
// however, the deposit will still revert if it wasn't signed with the validator private key
Copy link
Contributor

Choose a reason for hiding this comment

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

didn't get this

Copy link
Contributor

Choose a reason for hiding this comment

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

this function can't be permissionless

Copy link
Contributor

Choose a reason for hiding this comment

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

  • let's include accounting and 'outsourcing' for funding


// called by the staking vault owner if the predeposited validator has a different withdrawal credentials than the vault's withdrawal credentials,
// i.e. node operator was malicious
function withdrawDisprovenPredeposits(
Copy link
Contributor

Choose a reason for hiding this comment

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

not sure what should be done here

Copy link
Contributor

Choose a reason for hiding this comment

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

let's implement optionality in Dashboard (either fund stVault or withdraw to the outer address)

// set flag to false to prevent double withdrawal
validatorPredeposits[validatorId] = false;

(bool success, ) = _recipient.call{value: 1 ether}("");
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ maybe prevent direct calls to my stVault here (to not lose funds)

Copy link
Contributor

Choose a reason for hiding this comment

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

fixed

stakingVault.depositToBeaconChain(deposits);
}

function proveValidatorWithdrawalCredentials(
Copy link
Contributor

Choose a reason for hiding this comment

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

should combine prove + deposit

Copy link
Contributor

Choose a reason for hiding this comment

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

fixed

@Jeday Jeday changed the title [VAULTS][POC] a very raw bug-ridden proof-of-concept for PredepositGuardian [VAULTS][POC] PredepositGuarantee Feb 3, 2025
@Jeday Jeday changed the base branch from vault-guardian to feat/vaults February 3, 2025 13:27
Copy link
Contributor

@TheDZhon TheDZhon left a comment

Choose a reason for hiding this comment

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

👀

// Zero the remaining 16 bytes to form a 64‐byte block.
// (0x30 = 48, so mstore at 0x30 will zero 32 bytes covering addresses 48–79;
// only bytes 48–63 matter for our 64-byte input.)
mstore(0x30, 0)
Copy link
Contributor

Choose a reason for hiding this comment

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

it corrupts the free memory slot

Copy link
Contributor

Choose a reason for hiding this comment

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

Fixed

calldatacopy(0x00, pubkey.offset, 48)

// Zero the remaining 16 bytes to form a 64-byte input block
mstore(0x30, 0)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ it corrupts the free memory slot

Copy link
Contributor

Choose a reason for hiding this comment

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

image

Copy link
Contributor

Choose a reason for hiding this comment

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

fixed

* @custom:inOutDelta Net difference between ether funded and withdrawn from StakingVault
* @custom:nodeOperator Address of the node operator
* @custom:beaconChainDepositsPaused Whether beacon deposits are paused by the vault owner
* @custom: report Latest report containing valuation and inOutDelta
Copy link
Contributor Author

Choose a reason for hiding this comment

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

i think the space breaks the natspec custom tag directive

https://docs.soliditylang.org/en/latest/natspec-format.html#tags

*/
struct ERC7201Storage {
Report report;
uint128 locked;
int128 inOutDelta;
address nodeOperator;
address depositGuardian;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

let's rename to trustedDepositor, or something

Copy link
Contributor

Choose a reason for hiding this comment

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

just depositor is also ok, trusted kind of excessive

* @param _pubkey of validator that was proven invalid in PDG
* @param _recipient address to receive the `PREDEPOSIT_AMOUNT`
*/
function withdrawDisputedValidator(bytes calldata _pubkey, address _recipient) external {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why is it called "disputed"? It can either be proven of disproven?

Copy link
Contributor

Choose a reason for hiding this comment

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

Disproven ok

* Staking Vault does not have to be connected to Lido or any other system to be compatible with PDG
* but a reverse constraint can be AND are applied.
*/
contract PredepositGuarantee is CLProofVerifier, PausableUntilWithRoles {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why do need to pause it?

Copy link
Contributor

Choose a reason for hiding this comment

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

emeregency breaks by the dao

Copy link

github-actions bot commented Mar 2, 2025

badge

Hardhat Unit Tests Coverage Summary

Filename                                                                Stmts    Miss  Cover    Missing
--------------------------------------------------------------------  -------  ------  -------  -----------------------------------------------------------------------------------------------------------
contracts/0.4.24/Lido.sol                                                 204       6  97.06%   746, 751, 792-794, 951-952
contracts/0.4.24/StETH.sol                                                 79       0  100.00%
contracts/0.4.24/StETHPermit.sol                                           15       0  100.00%
contracts/0.4.24/lib/Packed64x4.sol                                         5       0  100.00%
contracts/0.4.24/lib/SigningKeys.sol                                       36       0  100.00%
contracts/0.4.24/lib/StakeLimitUtils.sol                                   37       0  100.00%
contracts/0.4.24/nos/NodeOperatorsRegistry.sol                            512       0  100.00%
contracts/0.4.24/oracle/LegacyOracle.sol                                   72       0  100.00%
contracts/0.4.24/utils/Pausable.sol                                         9       0  100.00%
contracts/0.4.24/utils/Versioned.sol                                        5       0  100.00%
contracts/0.6.12/WstETH.sol                                                17       0  100.00%
contracts/0.8.25/Accounting.sol                                            88       5  94.32%   114-117, 344, 370
contracts/0.8.25/interfaces/IDepositContract.sol                            0       0  100.00%
contracts/0.8.25/interfaces/ILido.sol                                       0       0  100.00%
contracts/0.8.25/interfaces/IOracleReportSanityChecker.sol                  0       0  100.00%
contracts/0.8.25/interfaces/IPostTokenRebaseReceiver.sol                    0       0  100.00%
contracts/0.8.25/interfaces/IStakingRouter.sol                              0       0  100.00%
contracts/0.8.25/interfaces/IWithdrawalQueue.sol                            0       0  100.00%
contracts/0.8.25/lib/GIndex.sol                                            33      18  45.45%   22, 34, 55, 63-70, 79, 86-101
contracts/0.8.25/lib/SSZ.sol                                               16      11  31.25%   31-100, 222-235
contracts/0.8.25/utils/AccessControlConfirmable.sol                        30       0  100.00%
contracts/0.8.25/utils/PausableUntilWithRoles.sol                           3       0  100.00%
contracts/0.8.25/vaults/Dashboard.sol                                      88       3  96.59%   377-386
contracts/0.8.25/vaults/Delegation.sol                                     40       0  100.00%
contracts/0.8.25/vaults/Permissions.sol                                    33       1  96.97%   267
contracts/0.8.25/vaults/StakingVault.sol                                  120       0  100.00%
contracts/0.8.25/vaults/VaultFactory.sol                                   48       0  100.00%
contracts/0.8.25/vaults/VaultHub.sol                                      172      36  79.07%   98, 285, 326-381, 519-528, 534-549
contracts/0.8.25/vaults/interfaces/IStakingVault.sol                        0       0  100.00%
contracts/0.8.25/vaults/predeposit_guarantee/CLProofVerifier.sol           25      12  52.00%   149-165, 178, 196
contracts/0.8.25/vaults/predeposit_guarantee/PredepositGuarantee.sol      137      64  53.28%   145-146, 164-174, 229-233, 246-260, 284, 289, 306, 310, 354, 363, 367, 407-506, 518, 523, 541-560, 584, 593
contracts/0.8.4/WithdrawalsManagerProxy.sol                                61       0  100.00%
contracts/0.8.9/BeaconChainDepositor.sol                                   21       2  90.48%   48, 51
contracts/0.8.9/Burner.sol                                                 72       0  100.00%
contracts/0.8.9/DepositSecurityModule.sol                                 128       0  100.00%
contracts/0.8.9/EIP712StETH.sol                                            16       0  100.00%
contracts/0.8.9/LidoExecutionLayerRewardsVault.sol                         16       0  100.00%
contracts/0.8.9/LidoLocator.sol                                            22       0  100.00%
contracts/0.8.9/OracleDaemonConfig.sol                                     28       0  100.00%
contracts/0.8.9/StakingRouter.sol                                         316       0  100.00%
contracts/0.8.9/WithdrawalQueue.sol                                        88       0  100.00%
contracts/0.8.9/WithdrawalQueueBase.sol                                   146       0  100.00%
contracts/0.8.9/WithdrawalQueueERC721.sol                                  89       0  100.00%
contracts/0.8.9/WithdrawalVault.sol                                        40       0  100.00%
contracts/0.8.9/lib/Math.sol                                                4       0  100.00%
contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol                         22      22  0.00%    88-172
contracts/0.8.9/lib/UnstructuredRefStorage.sol                              2       0  100.00%
contracts/0.8.9/oracle/AccountingOracle.sol                               190       2  98.95%   154-155
contracts/0.8.9/oracle/BaseOracle.sol                                      89       1  98.88%   397
contracts/0.8.9/oracle/HashConsensus.sol                                  263       1  99.62%   1005
contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol                         91       2  97.80%   138, 315
contracts/0.8.9/proxy/OssifiableProxy.sol                                  17       0  100.00%
contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol               218      50  77.06%   195, 232, 273-286, 306-324, 413-441, 495, 558-561, 569, 578, 586, 697, 702-747, 802
contracts/0.8.9/utils/DummyEmptyContract.sol                                0       0  100.00%
contracts/0.8.9/utils/PausableUntil.sol                                    31       0  100.00%
contracts/0.8.9/utils/Versioned.sol                                        11       0  100.00%
contracts/0.8.9/utils/access/AccessControl.sol                             23       0  100.00%
contracts/0.8.9/utils/access/AccessControlEnumerable.sol                    9       0  100.00%
contracts/common/utils/PausableUntil.sol                                   29       0  100.00%
contracts/testnets/sepolia/SepoliaDepositAdapter.sol                       21      21  0.00%    49-100
TOTAL                                                                    3887     257  93.39%

Diff against master

Filename                                                                Stmts    Miss  Cover
--------------------------------------------------------------------  -------  ------  --------
contracts/0.4.24/Lido.sol                                                  -8      +6  -2.94%
contracts/0.4.24/StETH.sol                                                 +7       0  +100.00%
contracts/0.8.25/Accounting.sol                                           +88      +5  +94.32%
contracts/0.8.25/interfaces/IDepositContract.sol                            0       0  +100.00%
contracts/0.8.25/interfaces/ILido.sol                                       0       0  +100.00%
contracts/0.8.25/interfaces/IOracleReportSanityChecker.sol                  0       0  +100.00%
contracts/0.8.25/interfaces/IPostTokenRebaseReceiver.sol                    0       0  +100.00%
contracts/0.8.25/interfaces/IStakingRouter.sol                              0       0  +100.00%
contracts/0.8.25/interfaces/IWithdrawalQueue.sol                            0       0  +100.00%
contracts/0.8.25/lib/GIndex.sol                                           +33     +18  +45.45%
contracts/0.8.25/lib/SSZ.sol                                              +16     +11  +31.25%
contracts/0.8.25/utils/AccessControlConfirmable.sol                       +30       0  +100.00%
contracts/0.8.25/utils/PausableUntilWithRoles.sol                          +3       0  +100.00%
contracts/0.8.25/vaults/Dashboard.sol                                     +88      +3  +96.59%
contracts/0.8.25/vaults/Delegation.sol                                    +40       0  +100.00%
contracts/0.8.25/vaults/Permissions.sol                                   +33      +1  +96.97%
contracts/0.8.25/vaults/StakingVault.sol                                 +120       0  +100.00%
contracts/0.8.25/vaults/VaultFactory.sol                                  +48       0  +100.00%
contracts/0.8.25/vaults/VaultHub.sol                                     +172     +36  +79.07%
contracts/0.8.25/vaults/interfaces/IStakingVault.sol                        0       0  +100.00%
contracts/0.8.25/vaults/predeposit_guarantee/CLProofVerifier.sol          +25     +12  +52.00%
contracts/0.8.25/vaults/predeposit_guarantee/PredepositGuarantee.sol     +137     +64  +53.28%
contracts/0.8.9/Burner.sol                                                 +1       0  +100.00%
contracts/0.8.9/LidoLocator.sol                                            +4       0  +100.00%
contracts/0.8.9/WithdrawalVault.sol                                       +19       0  +100.00%
contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol                          0     +22  -100.00%
contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol                          0     -89  +97.80%
contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol               -14     +50  -22.94%
contracts/common/utils/PausableUntil.sol                                  +29       0  +100.00%
TOTAL                                                                    +871    +139  -2.70%

Results for commit: b098888

Minimum allowed coverage is 90%

♻️ This comment has been updated with latest results

Copy link
Contributor

@TheDZhon TheDZhon left a comment

Choose a reason for hiding this comment

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

👀

Copy link
Contributor

Choose a reason for hiding this comment

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

can we roll it back?

Comment on lines 138 to 145
let result := staticcall(
gas(),
0x02,
0x00,
0x40,
0x00,
0x20
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
let result := staticcall(
gas(),
0x02,
0x00,
0x40,
0x00,
0x20
)
let result := staticcall(
gas(),
0x02, /* precompile */
0x00, /* input memory offset */
0x40, /* input length */
0x00, /* output memory offset */
0x20 /* output length */
)

Comment on lines 133 to 136
// Store elements to hash contiguously in scratch space.
// Scratch space is 64 bytes (0x00 - 0x3f) and both elements are 32 bytes.
mstore(scratch, leaf)
mstore(xor(scratch, 0x20), calldataload(offset))
Copy link
Contributor

Choose a reason for hiding this comment

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

it's basically to store to 0x00 and 0x20 not using the if condition

* @notice funds vault with ether of disproven validator from PDG
* @param _pubkey of validator that was proven invalid in PDG
*/
function refundDisputedValidatorToVault(bytes calldata _pubkey) external {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
function refundDisputedValidatorToVault(bytes calldata _pubkey) external {
function refundDisprovenValidatorToVault(bytes calldata _pubkey) external {

@@ -75,6 +76,16 @@ abstract contract Permissions is AccessControlConfirmable {
*/
bytes32 public constant VOLUNTARY_DISCONNECT_ROLE = keccak256("vaults.Permissions.VoluntaryDisconnect");

/**
* @notice Permission for recover assets from Delegate contracts
Copy link
Contributor

Choose a reason for hiding this comment

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

the comment is misleading (copypasta)

* Node operator address is set in the initialization and can never be changed.
*/
function nodeOperator() external view returns (address) {
return _getStorage().nodeOperator;
}

/**
* @notice Returns the address of the depositor
* Trusted party responsible for securely depositing validators to the beacon chain.
Copy link
Contributor

Choose a reason for hiding this comment

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

it's not clear what it is about (deposit front-run vuln)

* In the context of this contract, the depositor performs deposits through `depositToBeaconChain()`.
* Depositor address is set in the initialization and can be changed by the owner with `setDepositor`
* only on the condition that the vault is not connected to the VaultHub.
* @return Address of the deposit guardian
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* @return Address of the deposit guardian
* @return Address of the depositor

IStakingVault.Deposit calldata deposit = _deposits[i];
Deposit calldata deposit = _deposits[i];

//TODO: check BLS signature
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
//TODO: check BLS signature

revert DepositorAlreadySet();
}

VaultHub.VaultSocket memory socket = VaultHub(VAULT_HUB).vaultSocket(address(this));
Copy link
Contributor

Choose a reason for hiding this comment

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

unfortunately, it's better to avoid using VaultHub here

* @dev It can only be changed when vault is not connected to the VaultHub
*
*/
function setDepositor(address _depositor) external onlyOwner {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ let's remove setter

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
solidity issues/tasks related to smart contract code vaults
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants