Skip to content
Merged
Show file tree
Hide file tree
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
69 changes: 31 additions & 38 deletions src/Pectra.sol
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,14 @@ contract Pectra {
_;
}

function getFee(address target) public view returns (uint256 fee) {
(bool readOK, bytes memory feeData) = target.staticcall("");
function getConsolidationFee() public view returns (uint256 fee) {
(bool readOK, bytes memory feeData) = consolidationTarget.staticcall("");
if (!readOK) return MIN_FEE;
fee = uint256(bytes32(feeData));
}

function getExitFee() public view returns (uint256 fee) {
(bool readOK, bytes memory feeData) = exitTarget.staticcall("");
if (!readOK) return MIN_FEE;
fee = uint256(bytes32(feeData));
}
Expand All @@ -82,7 +88,7 @@ contract Pectra {
revert InvalidTargetPubkeyLength(targetPubkey);
}

uint256 consolidationFee = getFee(consolidationTarget);
uint256 consolidationFee = getConsolidationFee();
require(msg.value >= batchSize * consolidationFee, InsufficientFeePerValidator());

for (uint256 i = 0; i < batchSize; ++i) {
Expand All @@ -105,7 +111,7 @@ contract Pectra {
require(batchSize >= MIN_VALIDATORS, MinimumValidatorRequired());
require(batchSize <= MAX_VALIDATORS, TooManyValidators());

uint256 switchFee = getFee(consolidationTarget);
uint256 switchFee = getConsolidationFee();
require(msg.value >= batchSize * switchFee, InsufficientFeePerValidator());

for (uint256 i = 0; i < batchSize; ++i) {
Expand All @@ -123,58 +129,45 @@ contract Pectra {
}
}

function batchELExit(bytes[3][] calldata data) external payable onlySelf {
// Define the ExitData struct
struct ExitData {
bytes pubkey; // 48-byte validator public key
uint64 amount; // Amount in gwei (or zero for full exit)
bool isFullExit; // Safety flag requiring explicit confirmation for full exits
}

function batchELExit(ExitData[] calldata data) external payable onlySelf {
uint256 batchSize = data.length;
require(batchSize >= MIN_VALIDATORS, MinimumValidatorRequired());
require(batchSize <= MAX_VALIDATORS, TooManyValidators());

uint256 exitFee = getFee(exitTarget);
uint256 exitFee = getExitFee();
require(msg.value >= batchSize * exitFee, InsufficientFeePerValidator());

for (uint256 i = 0; i < batchSize; ++i) {
if (data[i][0].length != VALIDATOR_PUBKEY_LENGTH) {
emit ExecutionLayerExitFailed(INVALID_PUBKEY_LENGTH, data[i][0], data[i][1]);
continue;
}
if (data[i][1].length != AMOUNT_LENGTH) {
emit ExecutionLayerExitFailed(INVALID_AMOUNT_LENGTH, data[i][0], data[i][1]);
if (data[i].pubkey.length != VALIDATOR_PUBKEY_LENGTH) {
emit ExecutionLayerExitFailed(INVALID_PUBKEY_LENGTH, data[i].pubkey, abi.encodePacked(data[i].amount));
continue;
}

// Check if amount is zero (representing a full exit)
bool isZeroAmount = true;
for (uint256 j = 0; j < data[i][1].length; j++) {
if (data[i][1][j] != 0) {
isZeroAmount = false;
break;
}
}
bool isZeroAmount = data[i].amount == 0;

// For zero amount, require confirmation flag to be true (third element is a single byte where 0x01 = true)
if (isZeroAmount && (data[i][2].length == 0 || data[i][2][0] == 0)) {
emit ExecutionLayerExitFailed(FULL_EXIT_NOT_CONFIRMED, data[i][0], data[i][1]);
if (isZeroAmount && !data[i].isFullExit) {
emit ExecutionLayerExitFailed(FULL_EXIT_NOT_CONFIRMED, data[i].pubkey, abi.encodePacked(data[i].amount));
continue;
}

// Check if amount exceeds MAX_WITHDRAWAL_AMOUNT by converting bytes to uint64
if (!isZeroAmount) {
uint64 amount = 0;
// Parse amount as a big-endian uint64
for (uint256 j = 0; j < data[i][1].length; j++) {
amount = (amount << 8) | uint64(uint8(data[i][1][j]));
}

// Check if amount exceeds our maximum allowed value
if (amount > MAX_WITHDRAWAL_AMOUNT) {
emit ExecutionLayerExitFailed(AMOUNT_EXCEEDS_MAXIMUM, data[i][0], data[i][1]);
continue;
}
if (!isZeroAmount && data[i].amount > MAX_WITHDRAWAL_AMOUNT) {
emit ExecutionLayerExitFailed(AMOUNT_EXCEEDS_MAXIMUM, data[i].pubkey, abi.encodePacked(data[i].amount));
continue;
}

bytes memory concatenated = abi.encodePacked(data[i][0], data[i][1]);
bytes memory amountBytes = abi.encodePacked(data[i].amount);

bytes memory concatenated = abi.encodePacked(data[i].pubkey, amountBytes);
(bool success,) = exitTarget.call{value: exitFee}(concatenated);
if (!success) {
emit ExecutionLayerExitFailed(OPERATION_FAILED, data[i][0], data[i][1]);
emit ExecutionLayerExitFailed(OPERATION_FAILED, data[i].pubkey, amountBytes);
continue;
}
}
Expand Down
Loading