Skip to content

Commit

Permalink
Merge pull request #936 from lidofinance/predeposit-guardian-fixes
Browse files Browse the repository at this point in the history
feat: enhance PredepositGuardian with events, errors, fix bugs
  • Loading branch information
Jeday authored Feb 10, 2025
2 parents 16014b0 + 3576bb9 commit 7b61c6a
Showing 1 changed file with 65 additions and 39 deletions.
104 changes: 65 additions & 39 deletions contracts/0.8.25/vaults/predeposit_guarantee/PredepositGuarantee.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,40 @@ contract PredepositGuarantee is CLProofVerifier {
address nodeOperator;
}

// Events
event NodeOperatorBondToppedUp(address indexed nodeOperator, uint256 amount);
event NodeOperatorBondWithdrawn(address indexed nodeOperator, uint256 amount, address indexed recipient);
event NodeOperatorVoucherSet(address indexed nodeOperator, address indexed voucher);
event ValidatorPreDeposited(address indexed nodeOperator, address indexed stakingVault, uint256 numberOfDeposits, uint256 totalDepositAmount);
event ValidatorProven(address indexed nodeOperator, bytes indexed validatorPubkey, address indexed stakingVault, bytes32 withdrawalCredentials);
event ValidatorDisproven(address indexed nodeOperator, bytes indexed validatorPubkey, address indexed stakingVault, bytes32 withdrawalCredentials);
event ValidatorDisprovenWithdrawn(address indexed nodeOperator, bytes indexed validatorPubkey, address indexed stakingVault, address recipient);

constructor(GIndex _gIFirstValidator) CLProofVerifier(_gIFirstValidator) {}

mapping(address nodeOperator => NodeOperatorBond bond) public nodeOperatorBonds;
mapping(address nodeOperator => address voucher) public nodeOperatorVoucher;

mapping(bytes validatorPubkey => ValidatorStatus validatorStatus) public validatorStatuses;

/// NO Balance operations
// View functions

function topUpNodeOperatorBond(address _nodeOperator) external payable {
if (msg.value == 0) revert ZeroArgument("msg.value");
if (_nodeOperator == address(0)) revert ZeroArgument("_nodeOperator");
function nodeOperatorBond(address _nodeOperator) external view returns (NodeOperatorBond memory) {
return nodeOperatorBonds[_nodeOperator];
}

_validateNodeOperatorCaller(_nodeOperator);
function nodeOperatorVoucherAddress(address _nodeOperator) external view returns (address) {
return nodeOperatorVoucher[_nodeOperator];
}

nodeOperatorBonds[_nodeOperator].total += uint128(msg.value);
function validatorStatus(bytes calldata _validatorPubkey) external view returns (ValidatorStatus memory) {
return validatorStatuses[_validatorPubkey];
}

/// NO Balance operations

function topUpNodeOperatorBond(address _nodeOperator) external payable {
_topUpNodeOperatorCollateral(_nodeOperator);
}

function withdrawNodeOperatorBond(address _nodeOperator, uint128 _amount, address _recipient) external {
Expand All @@ -54,13 +72,16 @@ contract PredepositGuarantee is CLProofVerifier {

_validateNodeOperatorCaller(_nodeOperator);

if (nodeOperatorBonds[_nodeOperator].total - nodeOperatorBonds[_nodeOperator].locked < _amount)
revert NotEnoughUnlockedCollateralToWithdraw();
uint256 unlockedCollateral = nodeOperatorBonds[_nodeOperator].total - nodeOperatorBonds[_nodeOperator].locked;

if (unlockedCollateral < _amount)
revert NotEnoughUnlockedCollateralToWithdraw(unlockedCollateral, _amount);

nodeOperatorBonds[_nodeOperator].total -= _amount;
(bool success, ) = _recipient.call{value: uint256(_amount)}("");
if (!success) revert WithdrawalFailed();
// TODO: event

emit NodeOperatorBondWithdrawn(_nodeOperator, _amount, _recipient);
}

function setNodeOperatorVoucher(address _voucher) external {
Expand All @@ -81,7 +102,7 @@ contract PredepositGuarantee is CLProofVerifier {

nodeOperatorVoucher[msg.sender] = _voucher;

// TODO: event
emit NodeOperatorVoucherSet(msg.sender, _voucher);
}

/// Deposit operations
Expand All @@ -106,19 +127,20 @@ contract PredepositGuarantee is CLProofVerifier {
}

uint128 totalDepositAmount = PREDEPOSIT_AMOUNT * uint128(_deposits.length);
uint256 unlockedCollateral = nodeOperatorBonds[_nodeOperator].total - nodeOperatorBonds[_nodeOperator].locked;

if (nodeOperatorBonds[_nodeOperator].total - nodeOperatorBonds[_nodeOperator].locked < totalDepositAmount)
revert NotEnoughUnlockedCollateralToPredeposit();
if (unlockedCollateral < totalDepositAmount)
revert NotEnoughUnlockedCollateralToPredeposit(unlockedCollateral, totalDepositAmount);

for (uint256 i = 0; i < _deposits.length; i++) {
IStakingVaultOwnable.Deposit calldata _deposit = _deposits[i];

if (validatorStatuses[_deposit.pubkey].bondStatus != BondStatus.NO_RECORD) {
revert MustBeNewValidatorPubkey();
revert MustBeNewValidatorPubkey(_deposit.pubkey, validatorStatuses[_deposit.pubkey].bondStatus);
}

// cannot predeposit a validator with a deposit amount that is not 1 ether
if (_deposit.amount != PREDEPOSIT_AMOUNT) revert PredepositDepositAmountInvalid();
if (_deposit.amount != PREDEPOSIT_AMOUNT) revert PredepositDepositAmountInvalid(_deposit.pubkey, _deposit.amount);

validatorStatuses[_deposit.pubkey] = ValidatorStatus({
bondStatus: BondStatus.AWAITING_PROOF,
Expand All @@ -129,7 +151,8 @@ contract PredepositGuarantee is CLProofVerifier {

nodeOperatorBonds[_nodeOperator].locked += totalDepositAmount;
_stakingVault.depositToBeaconChain(_deposits);
// TODO: event

emit ValidatorPreDeposited(_nodeOperator, address(_stakingVault), _deposits.length, totalDepositAmount);
}

/*
Expand All @@ -145,7 +168,7 @@ contract PredepositGuarantee is CLProofVerifier {
function depositToProvenValidators(
IStakingVaultOwnable _stakingVault,
IStakingVaultOwnable.Deposit[] calldata _deposits
) public payable {
) public {
if (msg.sender != _stakingVault.nodeOperator()) {
revert MustBeNodeOperator();
}
Expand All @@ -154,11 +177,11 @@ contract PredepositGuarantee is CLProofVerifier {
IStakingVaultOwnable.Deposit calldata _deposit = _deposits[i];

if (validatorStatuses[_deposit.pubkey].bondStatus != BondStatus.PROVED) {
revert DepositToUnprovenValidator();
revert DepositToUnprovenValidator(_deposit.pubkey, validatorStatuses[_deposit.pubkey].bondStatus);
}

if (validatorStatuses[_deposit.pubkey].stakingVault != _stakingVault) {
revert DepositToWrongVault();
revert DepositToWrongVault(_deposit.pubkey, address(_stakingVault));
}
}

Expand Down Expand Up @@ -193,7 +216,7 @@ contract PredepositGuarantee is CLProofVerifier {
ValidatorStatus storage validatorStatus = validatorStatuses[_witness.pubkey];

if (validatorStatus.bondStatus != BondStatus.AWAITING_PROOF) {
revert ValidatorNotPreDeposited();
revert ValidatorNotPreDeposited(_witness.pubkey, validatorStatus.bondStatus);
}

if (address(validatorStatus.stakingVault) == _wcToAddress(_invalidWithdrawalCredentials)) {
Expand All @@ -208,7 +231,7 @@ contract PredepositGuarantee is CLProofVerifier {
// freed ether only will returned to owner of the vault with this validator
validatorStatus.bondStatus = BondStatus.PROVED_INVALID;

// TODO: events
emit ValidatorDisproven(validatorStatus.nodeOperator, _witness.pubkey, address(validatorStatus.stakingVault), _invalidWithdrawalCredentials);
}

// called by the staking vault owner if the predeposited validator was proven invalid
Expand All @@ -222,15 +245,15 @@ contract PredepositGuarantee is CLProofVerifier {

if (msg.sender != validatorStatus.stakingVault.owner()) revert WithdrawSenderNotStakingVaultOwner();

if (validatorStatus.bondStatus != BondStatus.PROVED_INVALID) revert ValidatorNotProvenInvalid();
if (validatorStatus.bondStatus != BondStatus.PROVED_INVALID) revert ValidatorNotProvenInvalid(validatorStatus.bondStatus);

validatorStatus.bondStatus = BondStatus.WITHDRAWN;

(bool success, ) = _recipient.call{value: PREDEPOSIT_AMOUNT}("");

if (!success) revert WithdrawalFailed();

//TODO: events
emit ValidatorDisprovenWithdrawn(validatorStatus.nodeOperator, validatorPubkey, address(validatorStatus.stakingVault), _recipient);
}

function disproveAndWithdraw(
Expand All @@ -251,7 +274,14 @@ contract PredepositGuarantee is CLProofVerifier {
}

function _topUpNodeOperatorCollateral(address _nodeOperator) internal {
// TODO: event
if (msg.value == 0) revert ZeroArgument("msg.value");
if (_nodeOperator == address(0)) revert ZeroArgument("_nodeOperator");

_validateNodeOperatorCaller(_nodeOperator);

nodeOperatorBonds[_nodeOperator].total += uint128(msg.value);

emit NodeOperatorBondToppedUp(_nodeOperator, msg.value);
}

function _wcToAddress(bytes32 _withdrawalCredentials) internal pure returns (address _wcAddress) {
Expand All @@ -268,7 +298,7 @@ contract PredepositGuarantee is CLProofVerifier {
ValidatorStatus storage validatorStatus = validatorStatuses[_witness.pubkey];

if (validatorStatus.bondStatus != BondStatus.AWAITING_PROOF) {
revert ValidatorNotPreDeposited();
revert ValidatorNotPreDeposited(_witness.pubkey, validatorStatus.bondStatus);
}

bytes32 _withdrawalCredentials = validatorStatus.stakingVault.withdrawalCredentials();
Expand All @@ -283,7 +313,7 @@ contract PredepositGuarantee is CLProofVerifier {
validatorStatus.bondStatus = BondStatus.PROVED;
nodeOperatorBonds[validatorStatus.nodeOperator].locked -= PREDEPOSIT_AMOUNT;

// TODO: positive events
emit ValidatorProven(validatorStatus.nodeOperator, _witness.pubkey, address(validatorStatus.stakingVault), _withdrawalCredentials);
}

// node operator accounting
Expand All @@ -292,33 +322,29 @@ contract PredepositGuarantee is CLProofVerifier {

// predeposit errors
error PredepositNoDeposits();
error PredepositDepositAmountInvalid(bytes validatorPubkey, uint256 depositAmount);
error MustBeNewValidatorPubkey(bytes validatorPubkey, BondStatus bondStatus);
error NotEnoughUnlockedCollateralToPredeposit(uint256 unlockedCollateral, uint256 totalDepositAmount);
error PredepositValueNotMultipleOfPrediposit();
error PredepositDepositAmountInvalid();
error MustBeNewValidatorPubkey();
error stakingVaultWithdrawalCredentialsMismatch();
error NotEnoughUnlockedCollateralToPredeposit();

// depositing errors
error DepositToUnprovenValidator();
error DepositToWrongVault();
error ValidatorNotPreDeposited();
error DepositToUnprovenValidator(bytes validatorPubkey, BondStatus bondStatus);
error DepositToWrongVault(bytes validatorPubkey, address stakingVault);
error ValidatorNotPreDeposited(bytes validatorPubkey, BondStatus bondStatus);

// prove
error WithdrawalCredentialsAreInvalid();

error WithdrawalCredentialsAreValid();
// withdrawal proven
error NotEnoughUnlockedCollateralToWithdraw();
error NotEnoughUnlockedCollateralToWithdraw(uint256 unlockedCollateral, uint256 amount);

// withdrawal disproven
error ValidatorNotProvenInvalid();
error ValidatorNotProvenInvalid(BondStatus bondStatus);
error WithdrawSenderNotStakingVaultOwner();
error WithdrawSenderNotNodeOperator();
error WithdrawValidatorDoesNotBelongToNodeOperator();
error WithdrawalCollateralOfWrongVault();
error WithdrawalCredentialsAreValid();
error WithdrawToVaultNotAllowed();
/// withdrawal generic
error WithdrawalFailed();
error WithdrawToVaultNotAllowed();

// auth
error MustBeNodeOperatorOrVoucher();
Expand Down

0 comments on commit 7b61c6a

Please sign in to comment.