Skip to content

Commit

Permalink
feat: revert if partial withdrawals are requested on the unhealthy vault
Browse files Browse the repository at this point in the history
  • Loading branch information
tamtamchik committed Feb 10, 2025
1 parent f0d865e commit 60f3e68
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 3 deletions.
20 changes: 17 additions & 3 deletions contracts/0.8.25/vaults/StakingVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ contract StakingVault is IStakingVault, OwnableUpgradeable {
/**
* @notice The type of withdrawal credentials for the validators deposited from this `StakingVault`.
*/
uint256 private constant WC_0x02_PREFIX = 0x02 << 248;
uint256 private constant WC_0X02_PREFIX = 0x02 << 248;

/**
* @notice The length of the public key in bytes
Expand Down Expand Up @@ -350,7 +350,7 @@ contract StakingVault is IStakingVault, OwnableUpgradeable {
* @return Withdrawal credentials as bytes32
*/
function withdrawalCredentials() public view returns (bytes32) {
return bytes32(WC_0x02_PREFIX | uint160(address(this)));
return bytes32(WC_0X02_PREFIX | uint160(address(this)));
}

/**
Expand Down Expand Up @@ -467,7 +467,16 @@ contract StakingVault is IStakingVault, OwnableUpgradeable {
if (_refundRecipient == address(0)) revert ZeroArgument("_refundRecipient");

ERC7201Storage storage $ = _getStorage();
if (msg.sender == $.nodeOperator || msg.sender == owner() || (valuation() < $.locked && msg.sender == address(VAULT_HUB))) {
bool isHealthy = valuation() >= $.locked;
if (!isHealthy) {
for (uint256 i = 0; i < _amounts.length; i++) {
if (_amounts[i] > 0) {
revert PartialWithdrawalsForbidden();
}
}
}

if (msg.sender == $.nodeOperator || msg.sender == owner() || (!isHealthy && msg.sender == address(VAULT_HUB))) {
uint256 feePerRequest = TriggerableWithdrawals.getWithdrawalRequestFee();
uint256 totalFee = (feePerRequest * _pubkeys.length) / PUBLIC_KEY_LENGTH;
if (value < totalFee) {
Expand Down Expand Up @@ -723,4 +732,9 @@ contract StakingVault is IStakingVault, OwnableUpgradeable {
* @param _amount Amount of ether to refund
*/
error ValidatorWithdrawalFeeRefundFailed(address _sender, uint256 _amount);

/**
* @notice Thrown when partial withdrawals are forbidden on an unhealthy vault
*/
error PartialWithdrawalsForbidden();
}
11 changes: 11 additions & 0 deletions test/0.8.25/vaults/staking-vault/stakingVault.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,17 @@ describe("StakingVault.sol", () => {
.to.emit(withdrawalRequest, "eip7002MockRequestAdded")
.withArgs(encodeEip7002Input(SAMPLE_PUBKEY, 0n), baseFee);
});

it("reverts if partial withdrawals is called on an unhealthy vault", async () => {
await stakingVault.fund({ value: ether("1") });
await stakingVault.connect(vaultHubSigner).report(ether("0.9"), ether("1"), ether("1.1")); // slashing

await expect(
stakingVault
.connect(vaultOwner)
.requestValidatorWithdrawals(SAMPLE_PUBKEY, [ether("1")], vaultOwnerAddress, { value: 1n }),
).to.be.revertedWithCustomError(stakingVault, "PartialWithdrawalsForbidden");
});
});

context("computeDepositDataRoot", () => {
Expand Down

0 comments on commit 60f3e68

Please sign in to comment.