diff --git a/src/Pectra.sol b/src/Pectra.sol index e94235e..757032f 100644 --- a/src/Pectra.sol +++ b/src/Pectra.sol @@ -21,17 +21,19 @@ contract Pectra { /// @dev Maximum withdrawal amount as a uint64 (representing 2048 ether in gwei) uint64 public constant MAX_WITHDRAWAL_AMOUNT = 0x1DCD6500000; - // Failure reason codes - uint8 public constant INVALID_PUBKEY_LENGTH = 1; - uint8 public constant OPERATION_FAILED = 2; - uint8 public constant INVALID_AMOUNT_LENGTH = 3; - uint8 public constant INVALID_AMOUNT_VALUE = 4; - uint8 public constant FULL_EXIT_NOT_CONFIRMED = 5; - uint8 public constant AMOUNT_EXCEEDS_MAXIMUM = 6; - - event ConsolidationFailed(uint8 reasonCode, bytes sourcePubkey, bytes targetPubkey); - event SwitchFailed(uint8 reasonCode, bytes pubkey); - event ExecutionLayerExitFailed(uint8 reasonCode, bytes pubkey, bytes amount); + // Failure reason codes as enum + enum FailureReason { + INVALID_PUBKEY_LENGTH, + OPERATION_FAILED, + INVALID_AMOUNT_LENGTH, + INVALID_AMOUNT_VALUE, + FULL_EXIT_NOT_CONFIRMED, + AMOUNT_EXCEEDS_MAXIMUM + } + + event ConsolidationFailed(FailureReason reasonCode, bytes sourcePubkey, bytes targetPubkey); + event SwitchFailed(FailureReason reasonCode, bytes pubkey); + event ExecutionLayerExitFailed(FailureReason reasonCode, bytes pubkey, uint64 amount); error Unauthorized(); error InvalidTargetPubkeyLength(bytes invalidTargetPubkey); @@ -93,14 +95,14 @@ contract Pectra { for (uint256 i = 0; i < batchSize; ++i) { if (sourcePubkeys[i].length != VALIDATOR_PUBKEY_LENGTH) { - emit ConsolidationFailed(INVALID_PUBKEY_LENGTH, sourcePubkeys[i], targetPubkey); + emit ConsolidationFailed(FailureReason.INVALID_PUBKEY_LENGTH, sourcePubkeys[i], targetPubkey); continue; } bytes memory concatenated = abi.encodePacked(sourcePubkeys[i], targetPubkey); (bool success,) = consolidationTarget.call{value: consolidationFee}(concatenated); if (!success) { - emit ConsolidationFailed(OPERATION_FAILED, sourcePubkeys[i], targetPubkey); + emit ConsolidationFailed(FailureReason.OPERATION_FAILED, sourcePubkeys[i], targetPubkey); continue; } } @@ -116,14 +118,14 @@ contract Pectra { for (uint256 i = 0; i < batchSize; ++i) { if (pubkeys[i].length != VALIDATOR_PUBKEY_LENGTH) { - emit SwitchFailed(INVALID_PUBKEY_LENGTH, pubkeys[i]); + emit SwitchFailed(FailureReason.INVALID_PUBKEY_LENGTH, pubkeys[i]); continue; } bytes memory concatenated = abi.encodePacked(pubkeys[i], pubkeys[i]); (bool success,) = consolidationTarget.call{value: switchFee}(concatenated); if (!success) { - emit SwitchFailed(OPERATION_FAILED, pubkeys[i]); + emit SwitchFailed(FailureReason.OPERATION_FAILED, pubkeys[i]); continue; } } @@ -146,19 +148,19 @@ contract Pectra { for (uint256 i = 0; i < batchSize; ++i) { if (data[i].pubkey.length != VALIDATOR_PUBKEY_LENGTH) { - emit ExecutionLayerExitFailed(INVALID_PUBKEY_LENGTH, data[i].pubkey, abi.encodePacked(data[i].amount)); + emit ExecutionLayerExitFailed(FailureReason.INVALID_PUBKEY_LENGTH, data[i].pubkey, data[i].amount); continue; } bool isZeroAmount = data[i].amount == 0; if (isZeroAmount && !data[i].isFullExit) { - emit ExecutionLayerExitFailed(FULL_EXIT_NOT_CONFIRMED, data[i].pubkey, abi.encodePacked(data[i].amount)); + emit ExecutionLayerExitFailed(FailureReason.FULL_EXIT_NOT_CONFIRMED, data[i].pubkey, data[i].amount); continue; } if (!isZeroAmount && data[i].amount > MAX_WITHDRAWAL_AMOUNT) { - emit ExecutionLayerExitFailed(AMOUNT_EXCEEDS_MAXIMUM, data[i].pubkey, abi.encodePacked(data[i].amount)); + emit ExecutionLayerExitFailed(FailureReason.AMOUNT_EXCEEDS_MAXIMUM, data[i].pubkey, data[i].amount); continue; } @@ -167,7 +169,7 @@ contract Pectra { bytes memory concatenated = abi.encodePacked(data[i].pubkey, amountBytes); (bool success,) = exitTarget.call{value: exitFee}(concatenated); if (!success) { - emit ExecutionLayerExitFailed(OPERATION_FAILED, data[i].pubkey, amountBytes); + emit ExecutionLayerExitFailed(FailureReason.OPERATION_FAILED, data[i].pubkey, data[i].amount); continue; } } diff --git a/test/Pectra.t.sol b/test/Pectra.t.sol index 3c0e00b..4a2ae97 100644 --- a/test/Pectra.t.sol +++ b/test/Pectra.t.sol @@ -154,8 +154,7 @@ contract PectraTest is Test { sources[0] = new bytes(pectra.VALIDATOR_PUBKEY_LENGTH() - 1); bytes memory target = validPubkey(); vm.expectEmit(true, true, true, true); - uint8 reasonCode = pectra.INVALID_PUBKEY_LENGTH(); - emit Pectra.ConsolidationFailed(reasonCode, sources[0], target); + emit Pectra.ConsolidationFailed(Pectra.FailureReason.INVALID_PUBKEY_LENGTH, sources[0], target); vm.prank(address(pectra)); pectra.batchConsolidation{value: 1}(sources, target); } @@ -168,8 +167,7 @@ contract PectraTest is Test { sources[0] = validPubkey(); bytes memory target = validPubkey(); vm.expectEmit(true, true, true, true); - uint8 reasonCode = pectra.OPERATION_FAILED(); - emit Pectra.ConsolidationFailed(reasonCode, sources[0], target); + emit Pectra.ConsolidationFailed(Pectra.FailureReason.OPERATION_FAILED, sources[0], target); vm.prank(address(pectra)); pectra.batchConsolidation{value: 1}(sources, target); // Restore successful code. @@ -232,8 +230,7 @@ contract PectraTest is Test { bytes[] memory pubkeys = new bytes[](1); pubkeys[0] = new bytes(pectra.VALIDATOR_PUBKEY_LENGTH() - 1); // one byte less than required vm.expectEmit(true, true, true, true); - uint8 reasonCode = pectra.INVALID_PUBKEY_LENGTH(); - emit Pectra.SwitchFailed(reasonCode, pubkeys[0]); + emit Pectra.SwitchFailed(Pectra.FailureReason.INVALID_PUBKEY_LENGTH, pubkeys[0]); vm.prank(address(pectra)); pectra.batchSwitch{value: 1}(pubkeys); } @@ -243,8 +240,7 @@ contract PectraTest is Test { bytes[] memory pubkeys = new bytes[](1); pubkeys[0] = validPubkey(); vm.expectEmit(true, true, true, true); - uint8 reasonCode = pectra.OPERATION_FAILED(); - emit Pectra.SwitchFailed(reasonCode, pubkeys[0]); + emit Pectra.SwitchFailed(Pectra.FailureReason.OPERATION_FAILED, pubkeys[0]); vm.prank(address(pectra)); pectra.batchSwitch{value: 1}(pubkeys); vm.etch(consolidationTarget, feeCode); @@ -310,8 +306,7 @@ contract PectraTest is Test { data[0].amount = 0; data[0].isFullExit = true; vm.expectEmit(true, true, true, true); - uint8 reasonCode = pectra.INVALID_PUBKEY_LENGTH(); - emit Pectra.ExecutionLayerExitFailed(reasonCode, data[0].pubkey, abi.encodePacked(data[0].amount)); + emit Pectra.ExecutionLayerExitFailed(Pectra.FailureReason.INVALID_PUBKEY_LENGTH, data[0].pubkey, data[0].amount); vm.prank(address(pectra)); pectra.batchELExit{value: 1}(data); } @@ -322,8 +317,9 @@ contract PectraTest is Test { data[0].amount = 0; // Zero amount data[0].isFullExit = false; // Flag set to false vm.expectEmit(true, true, true, true); - uint8 reasonCode = pectra.FULL_EXIT_NOT_CONFIRMED(); - emit Pectra.ExecutionLayerExitFailed(reasonCode, data[0].pubkey, abi.encodePacked(data[0].amount)); + emit Pectra.ExecutionLayerExitFailed( + Pectra.FailureReason.FULL_EXIT_NOT_CONFIRMED, data[0].pubkey, data[0].amount + ); vm.prank(address(pectra)); pectra.batchELExit{value: 1}(data); } @@ -336,8 +332,9 @@ contract PectraTest is Test { data[0].isFullExit = true; // Not needed but included for consistency vm.expectEmit(true, true, true, true); - uint8 reasonCode = pectra.AMOUNT_EXCEEDS_MAXIMUM(); - emit Pectra.ExecutionLayerExitFailed(reasonCode, data[0].pubkey, abi.encodePacked(data[0].amount)); + emit Pectra.ExecutionLayerExitFailed( + Pectra.FailureReason.AMOUNT_EXCEEDS_MAXIMUM, data[0].pubkey, data[0].amount + ); vm.prank(address(pectra)); pectra.batchELExit{value: 1}(data); @@ -350,8 +347,7 @@ contract PectraTest is Test { data[0].amount = 1000000000; // 1 ether in gwei data[0].isFullExit = true; // Not needed but included for consistency vm.expectEmit(true, true, true, true); - uint8 reasonCode = pectra.OPERATION_FAILED(); - emit Pectra.ExecutionLayerExitFailed(reasonCode, data[0].pubkey, abi.encodePacked(data[0].amount)); + emit Pectra.ExecutionLayerExitFailed(Pectra.FailureReason.OPERATION_FAILED, data[0].pubkey, data[0].amount); vm.prank(address(pectra)); pectra.batchELExit{value: 1}(data); vm.etch(exitTarget, feeCode);