Skip to content

Commit

Permalink
Merge branch 'feat/withdrawal-credentials' into feat/vaults-triggerab…
Browse files Browse the repository at this point in the history
…le-exits
  • Loading branch information
tamtamchik committed Feb 4, 2025
2 parents 1b11c66 + c27de34 commit c9d3226
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 12 deletions.
7 changes: 6 additions & 1 deletion contracts/common/lib/TriggerableWithdrawals.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ pragma solidity >=0.8.9 <0.9.0;
* Allow validators to trigger withdrawals and exits from their execution layer (0x01) withdrawal credentials.
*/
library TriggerableWithdrawals {
address constant WITHDRAWAL_REQUEST = 0x0c15F14308530b7CDB8460094BbB9cC28b9AaaAA;
address constant WITHDRAWAL_REQUEST = 0x00000961Ef480Eb55e80D19ad83579A64c007002;

uint256 internal constant PUBLIC_KEY_LENGTH = 48;
uint256 internal constant WITHDRAWAL_AMOUNT_LENGTH = 8;
uint256 internal constant WITHDRAWAL_REQUEST_CALLDATA_LENGTH = 56;

error WithdrawalFeeReadFailed();
error WithdrawalFeeInvalidData();
error WithdrawalRequestAdditionFailed(bytes callData);

error InsufficientWithdrawalFee(uint256 feePerRequest, uint256 minFeePerRequest);
Expand Down Expand Up @@ -156,6 +157,10 @@ library TriggerableWithdrawals {
revert WithdrawalFeeReadFailed();
}

if (feeData.length != 32) {
revert WithdrawalFeeInvalidData();
}

return abi.decode(feeData, (uint256));
}

Expand Down
6 changes: 3 additions & 3 deletions test/0.8.25/vaults/staking-vault/stakingVault.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,7 @@ describe("StakingVault.sol", () => {
});

it("makes a full validator withdrawal when called by the node operator", async () => {
const fee = await withdrawalRequest.fee();
const fee = BigInt(await withdrawalRequest.fee());
const amount = ether("32");

await expect(stakingVault.connect(operator).initiateFullValidatorWithdrawal(SAMPLE_PUBKEY, { value: amount }))
Expand Down Expand Up @@ -652,8 +652,8 @@ describe("StakingVault.sol", () => {
});

it("makes a partial validator withdrawal when called by the owner", async () => {
const fee = BigInt(await withdrawalRequest.fee());
const amount = ether("32");
const fee = await withdrawalRequest.fee();

await expect(
stakingVault
Expand All @@ -667,8 +667,8 @@ describe("StakingVault.sol", () => {
});

it("makes a partial validator withdrawal when called by the node operator", async () => {
const fee = BigInt(await withdrawalRequest.fee());
const amount = ether("32");
const fee = await withdrawalRequest.fee();

await expect(
stakingVault
Expand Down
21 changes: 21 additions & 0 deletions test/0.8.9/withdrawalVault.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,14 @@ describe("WithdrawalVault.sol", () => {
await withdrawalsPredeployed.setFailOnGetFee(true);
await expect(vault.getWithdrawalRequestFee()).to.be.revertedWithCustomError(vault, "WithdrawalFeeReadFailed");
});

["0x", "0x01", "0x" + "0".repeat(61) + "1", "0x" + "0".repeat(65) + "1"].forEach((unexpectedFee) => {
it(`Shoud revert if unexpected fee value ${unexpectedFee} is returned`, async function () {
await withdrawalsPredeployed.setFeeRaw(unexpectedFee);

await expect(vault.getWithdrawalRequestFee()).to.be.revertedWithCustomError(vault, "WithdrawalFeeInvalidData");
});
});
});

async function getFee(): Promise<bigint> {
Expand Down Expand Up @@ -387,6 +395,19 @@ describe("WithdrawalVault.sol", () => {
).to.be.revertedWithCustomError(vault, "WithdrawalFeeReadFailed");
});

["0x", "0x01", "0x" + "0".repeat(61) + "1", "0x" + "0".repeat(65) + "1"].forEach((unexpectedFee) => {
it(`Shoud revert if unexpected fee value ${unexpectedFee} is returned`, async function () {
await withdrawalsPredeployed.setFeeRaw(unexpectedFee);

const { pubkeysHexString } = generateWithdrawalRequestPayload(2);
const fee = 10n;

await expect(
vault.connect(validatorsExitBus).addFullWithdrawalRequests(pubkeysHexString, { value: fee }),
).to.be.revertedWithCustomError(vault, "WithdrawalFeeInvalidData");
});
});

it("should revert if refund failed", async function () {
const refundFailureTester: RefundFailureTester = await ethers.deployContract("RefundFailureTester", [
vaultAddress,
Expand Down
13 changes: 8 additions & 5 deletions test/common/contracts/EIP7002WithdrawalRequest_Mock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pragma solidity 0.8.9;
* @notice This is a mock of EIP-7002's pre-deploy contract.
*/
contract EIP7002WithdrawalRequest_Mock {
uint256 public fee;
bytes public fee;
bool public failOnAddRequest;
bool public failOnGetFee;

Expand All @@ -23,15 +23,18 @@ contract EIP7002WithdrawalRequest_Mock {

function setFee(uint256 _fee) external {
require(_fee > 0, "fee must be greater than 0");
fee = _fee;
fee = abi.encode(_fee);
}

fallback(bytes calldata input) external payable returns (bytes memory output) {
function setFeeRaw(bytes calldata _rawFeeBytes) external {
fee = _rawFeeBytes;
}

fallback(bytes calldata input) external payable returns (bytes memory) {
if (input.length == 0) {
require(!failOnGetFee, "fail on get fee");

output = abi.encode(fee);
return output;
return fee;
}

require(!failOnAddRequest, "fail on add request");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,17 @@ describe("TriggerableWithdrawals.sol", () => {
"WithdrawalFeeReadFailed",
);
});

["0x", "0x01", "0x" + "0".repeat(61) + "1", "0x" + "0".repeat(65) + "1"].forEach((unexpectedFee) => {
it(`Shoud revert if unexpected fee value ${unexpectedFee} is returned`, async function () {
await withdrawalsPredeployed.setFeeRaw(unexpectedFee);

await expect(triggerableWithdrawals.getWithdrawalRequestFee()).to.be.revertedWithCustomError(
triggerableWithdrawals,
"WithdrawalFeeInvalidData",
);
});
});
});

context("add triggerable withdrawal requests", () => {
Expand Down Expand Up @@ -265,6 +276,28 @@ describe("TriggerableWithdrawals.sol", () => {
).to.be.revertedWithCustomError(triggerableWithdrawals, "WithdrawalFeeReadFailed");
});

["0x", "0x01", "0x" + "0".repeat(61) + "1", "0x" + "0".repeat(65) + "1"].forEach((unexpectedFee) => {
it(`Shoud revert if unexpected fee value ${unexpectedFee} is returned`, async function () {
await withdrawalsPredeployed.setFeeRaw(unexpectedFee);

const { pubkeysHexString, partialWithdrawalAmounts, mixedWithdrawalAmounts } =
generateWithdrawalRequestPayload(2);
const fee = 10n;

await expect(
triggerableWithdrawals.addFullWithdrawalRequests(pubkeysHexString, fee),
).to.be.revertedWithCustomError(triggerableWithdrawals, "WithdrawalFeeInvalidData");

await expect(
triggerableWithdrawals.addPartialWithdrawalRequests(pubkeysHexString, partialWithdrawalAmounts, fee),
).to.be.revertedWithCustomError(triggerableWithdrawals, "WithdrawalFeeInvalidData");

await expect(
triggerableWithdrawals.addWithdrawalRequests(pubkeysHexString, mixedWithdrawalAmounts, fee),
).to.be.revertedWithCustomError(triggerableWithdrawals, "WithdrawalFeeInvalidData");
});
});

it("Should accept withdrawal requests with minimal possible fee when fee not provided", async function () {
const requestCount = 3;
const { pubkeysHexString, pubkeys, fullWithdrawalAmounts, partialWithdrawalAmounts, mixedWithdrawalAmounts } =
Expand Down
2 changes: 1 addition & 1 deletion test/common/lib/triggerableWithdrawals/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ethers } from "hardhat";

import { EIP7002WithdrawalRequest_Mock } from "typechain-types";

export const withdrawalsPredeployedHardcodedAddress = "0x0c15F14308530b7CDB8460094BbB9cC28b9AaaAA";
export const withdrawalsPredeployedHardcodedAddress = "0x00000961Ef480Eb55e80D19ad83579A64c007002";

export async function deployWithdrawalsPredeployedMock(
defaultRequestFee: bigint,
Expand Down
2 changes: 1 addition & 1 deletion test/deploy/stakingVault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export async function deployStakingVaultBehindBeaconProxy(
vaultOwner: HardhatEthersSigner,
operator: HardhatEthersSigner,
): Promise<DeployedStakingVault> {
// ERC7002 pre-deployed contract mock (0x0c15F14308530b7CDB8460094BbB9cC28b9AaaAA)
// ERC7002 pre-deployed contract mock (0x00000961Ef480Eb55e80D19ad83579A64c007002)
await deployWithdrawalsPreDeployedMock(1n);

// deploying implementation
Expand Down
2 changes: 1 addition & 1 deletion test/suite/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ export const SHARE_RATE_PRECISION = BigInt(10 ** 27);

export const ZERO_HASH = new Uint8Array(32).fill(0);

export const EIP7002_PREDEPLOYED_ADDRESS = "0x0c15F14308530b7CDB8460094BbB9cC28b9AaaAA";
export const EIP7002_PREDEPLOYED_ADDRESS = "0x00000961Ef480Eb55e80D19ad83579A64c007002";

0 comments on commit c9d3226

Please sign in to comment.