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

feat: enhance PredepositGuardian with events, errors, fix bugs #936

Merged
merged 3 commits into from
Feb 10, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading