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 46 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.

👀

bytes32 _expectedGlobalDepositRoot,
bytes calldata _signature
) external {
function depositToBeaconChain(Deposit[] calldata _deposits) external {
Copy link
Contributor

Choose a reason for hiding this comment

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

strawman sanity checks would be needed for the input data

Copy link
Contributor

Choose a reason for hiding this comment

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

Added BLS check todo, needs clarification on best use for precompile

@@ -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

contracts/0.8.25/vaults/PredepositGuardian.sol Outdated Show resolved Hide resolved
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 Author

Choose a reason for hiding this comment

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

this looks like it should be collected under a library

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, but

  • using allows it act as one
  • this 100% unchanged CSM code, which is easier to trust and audit

* @notice Interface for the `StakingVault` contract with `owner()` method from OZ `OwnableUpgradeable`
*/
interface IStakingVaultOwnable is IStakingVault {
function owner() external view returns (address);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Consider adding this to the interface above

Copy link
Contributor

Choose a reason for hiding this comment

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

The reason it's outside is because owner() cannot be added to IStakingVaults due to conflicts inside StakingVault without adding extra hack code to StakingVault contract. This interface avoids changes to contract.

@Jeday Jeday marked this pull request as ready for review February 10, 2025 15:51
@Jeday Jeday requested a review from a team as a code owner February 10, 2025 15:51
@Jeday Jeday changed the title [VAULTS][POC] PredepositGuarantee [VAULTS] PredepositGuarantee Feb 10, 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.

👀

contracts/0.8.25/lib/SSZ.sol Outdated Show resolved Hide resolved
/// @solidity memory-safe-assembly
assembly {
// Store `left` at memory position 0x00
mstore(0x00, left)
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 revisit this later: 'The zero slot is used as initial value for dynamic memory arrays and should never be written to (the free memory pointer points to 0x80 initially)'

https://docs.soliditylang.org/en/latest/internals/layout_in_memory.html

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 okay to use 64 bytes of scratch space to store precomile input, it will be overwritten by staticall.

// 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

* @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?

* 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?

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