Skip to content

Conversation

@iamsahu
Copy link
Contributor

@iamsahu iamsahu commented Feb 6, 2026

Description

This pull request updates the validator exit request mechanism in the operators registry to allow more granular, operator-specific exit requests and improves validation and testing for this process. The main changes include changing the requestValidatorExits function to accept explicit operator allocations, adding new error types for better input validation, and updating tests to reflect the new interface and logic.

Validator exit request improvements

  • Changed the requestValidatorExits function in OperatorsRegistryV1 and its interface to accept an array of OperatorAllocation structs instead of a single count, allowing precise allocation of exit requests per operator. The function now validates that allocations are non-empty, ordered, do not exceed operator-funded counts, and do not exceed overall exit demand. [1] [2]
  • Added new error types to IOperatorsRegistryV1 for invalid exit requests: ExitsRequestedExceedsFundedCount, ExitsRequestedExceedsDemand, and improved input validation with InvalidEmptyArray and UnorderedOperatorList.

Testing and mocks

  • Updated the RiverMock contract to support a keeper address and expose it via getKeeper, supporting the new access control in exit requests. [1] [2]
  • Updated test setup in OperatorsRegistry.1.t.sol to initialize and use the keeper address, and modified all tests to use the new requestValidatorExits interface with operator allocations and the keeper as the caller. [1] [2] [3] [4] [5] [6] [7] [8]

Notice

  • Have you checked to ensure there aren't other open Pull Requests for the same update/change?
  • Have you assigned this PR to yourself?
  • Have you added at least 1 reviewer?
  • Have you updated the official documentation?
  • Have you added sufficient documentation in your code?
  • Have you added relevant tests to the official test suite?

Pull Request Type

  • 💫 New Feature (Breaking Change)
  • 💫 New Feature (Non-breaking Change)
  • 🛠️ Bug fix (Non-breaking Change: Fixes an issue)
  • 🕹️ Chore (Non-breaking Change: Doc updates, pkg upgrades, typos, etc..)

Breaking changes (if applicable)

The signature for the function requestValidatorExits have been changed.

Testing

  • Have you tested this code with the official test suite?
  • Have you tested this code manually?

Summary by CodeRabbit

  • API Changes

    • Validator exit request now accepts a batch of per-operator allocations instead of a single count.
  • Validation & Error Handling

    • Added checks preventing per-operator requests from exceeding funded amounts and total requests from exceeding current demand.
    • New explicit errors for these validation failures.
  • Improvements

    • Per-operator exit accounting, demand updates and events for each update.
  • Access Control

    • Keeper-enforced restriction on exit requests.

@coderabbitai
Copy link

coderabbitai bot commented Feb 6, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The pull request refactors validator exit request handling from accepting a single count to processing batched, per-operator allocations with keeper authorization. It introduces comprehensive validation (ordering, funding limits, demand consistency) and updates state management accordingly, while removing the previous validator selection logic.

Changes

Cohort / File(s) Summary
Validator Exit Request API & Core Logic
contracts/src/OperatorsRegistry.1.sol
API change: requestValidatorExits now accepts OperatorAllocation[] array instead of single count. Adds keeper authorization, validates strictly increasing operator indices, enforces exit count limits against funded amounts, verifies total demand consistency, and updates state via _setTotalValidatorExitsRequested and _setCurrentValidatorExitsDemand. Removes internal validator selection utilities.
Interface Definitions
contracts/src/interfaces/IOperatorRegistry.1.sol
Updated requestValidatorExits signature to accept OperatorAllocation[] calldata. Added two new errors: ExitsRequestedExceedsFundedCount and ExitsRequestedExceedsDemand for granular validation feedback.
Test Suite & Mocks
contracts/test/OperatorsRegistry.1.t.sol
Removed debug function debugGetNextValidatorsToExitFromActiveOperators. Enhanced RiverMock with keeper state and accessors (setKeeper, getKeeper). Added keeper initialization in test base. Introduced test events UpdatedRequestedValidatorExitsUponStopped and SetCurrentValidatorExitsDemand for observation.

Sequence Diagram

sequenceDiagram
    participant Keeper as Keeper
    participant Registry as OperatorsRegistry
    participant River as RiverAddress
    
    Keeper->>Registry: requestValidatorExits(allocations[])
    activate Registry
    Registry->>River: Verify keeper authorization
    activate River
    River-->>Registry: keeper confirmed
    deactivate River
    
    Registry->>Registry: Validate demand > 0
    Registry->>Registry: Iterate allocations
    
    loop For each allocation
        Registry->>Registry: Verify operatorIndex ordering
        Registry->>Registry: Check operator is active
        Registry->>Registry: Validate exit count ≤ (funded - requested)
        Registry->>Registry: Update operator.requestedExits
        Registry->>Registry: Emit RequestedValidatorExits
    end
    
    Registry->>Registry: Verify totalExits ≤ currentDemand
    Registry->>Registry: Reduce currentValidatorExitsDemand
    Registry->>Registry: Update TotalValidatorExitsRequested
    Registry->>Registry: Emit UpdatedTotalValidatorExitsRequested
    
    deactivate Registry
    Keeper-->>Keeper: Exit requests processed
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Exits batched with keeper's care,
Allocations sorted, validated fair,
Demand and funding dance in sync,
No validators picked on a whim's brink,
State flows true through ordered array,
Logic refined in the modern way! 🌱

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'BYOV Exit Flow (BS-2454)' is vague and lacks specificity about the actual technical changes made. Provide a more descriptive title that conveys the main change, e.g., 'Change requestValidatorExits to accept operator allocations array' or 'Refactor validator exit requests to per-operator allocation model'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The description comprehensively covers the changes, includes breaking change notification, and addresses most template requirements with clear technical context.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/byov-v1-exit

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Feb 6, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.25%. Comparing base (2577c48) to head (1590ef2).
⚠️ Report is 14 commits behind head on feat/byov-v1-clean.

Additional details and impacted files
@@                  Coverage Diff                   @@
##           feat/byov-v1-clean     #341      +/-   ##
======================================================
+ Coverage               94.90%   95.25%   +0.34%     
======================================================
  Files                      77       77              
  Lines                    1807     1771      -36     
  Branches                  244      241       -3     
======================================================
- Hits                     1715     1687      -28     
+ Misses                     32       31       -1     
+ Partials                   60       53       -7     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@iamsahu iamsahu marked this pull request as ready for review February 9, 2026 14:17
@iamsahu iamsahu self-assigned this Feb 9, 2026
@iamsahu
Copy link
Contributor Author

iamsahu commented Feb 9, 2026

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 9, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
contracts/src/OperatorsRegistry.1.sol (1)

568-675: ⚠️ Potential issue | 🔴 Critical

CI is failing: “Stack too deep” in _setStoppedValidatorCounts.
This is a blocker. The compiler error points to Line 623, which is inside this function. Please refactor to reduce stack usage (e.g., split into smaller internal helpers, or move more state into a struct passed by reference). Enabling viaIR is an alternative if acceptable for your build.

🧹 Nitpick comments (2)
contracts/src/interfaces/IOperatorRegistry.1.sol (1)

358-362: Update the docstring to reflect explicit per-operator allocations.
The comment still mentions a selection algorithm, which no longer applies.

✍️ Suggested doc tweak
-    /// `@notice` The selection algorithm will pick validators based on their active validator counts
-    /// `@notice` This value is computed by using the count of funded keys and taking into account the stopped validator counts and exit requests
+    /// `@notice` Caller supplies explicit per-operator exit allocations (sorted by operator index)
+    /// `@notice` Total allocated exits must not exceed current exit demand
contracts/src/OperatorsRegistry.1.sol (1)

468-509: Avoid emitting no-op exit events for zero allocations.
Zero-count entries currently still touch storage and emit RequestedValidatorExits, which can be misleading and waste gas. Consider skipping them (or explicitly reverting) to keep the event stream clean.

♻️ Possible refinement (skip zero-count entries)
-            uint256 count = _allocations[i].validatorCount;
-            suppliedExitCount += count;
+            uint256 count = _allocations[i].validatorCount;

             if (i > 0 && !(operatorIndex > prevOperatorIndex)) {
                 revert UnorderedOperatorList();
             }
             prevOperatorIndex = operatorIndex;

             OperatorsV2.Operator storage operator = OperatorsV2.get(operatorIndex);
             if (!operator.active) {
                 revert InactiveOperator(operatorIndex);
             }
+            if (count == 0) {
+                continue;
+            }
+            suppliedExitCount += count;

Copy link
Contributor

@juliaaschmidt juliaaschmidt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@contracts/src/OperatorsRegistry.1.sol`:
- Around line 485-488: The revert ExitsRequestedExceedsFundedCount currently
passes operator.funded which is the total funded count instead of the available
exits; change the third argument to the available count (operator.funded -
operator.requestedExits) or compute a local variable like available =
operator.funded - operator.requestedExits and pass that to
ExitsRequestedExceedsFundedCount(operatorIndex, count, available) so the error
reports the correct available exits; update the revert call in the same block
where the if checks count > (operator.funded - operator.requestedExits).
- Around line 490-491: operator.requestedExits is being increased by casting
count (from OperatorAllocation.validatorCount, a uint256) to uint32 which can
truncate; add a guard to ensure count <= type(uint32).max before casting (revert
with a clear message) or use a SafeCast-to-uint32 utility to safely convert, or
change OperatorAllocation.validatorCount to uint32 at the type level; update the
logic around operator.requestedExits and the emit
RequestedValidatorExits(operatorIndex, operator.requestedExits) accordingly so
no silent truncation can occur.
🧹 Nitpick comments (3)
contracts/src/interfaces/IOperatorRegistry.1.sol (1)

199-208: Inconsistent error naming: ExitsRequestedExceedDemand vs ExitsRequestedExceedsFundedCount.

ExitsRequestedExceedsFundedCount uses "Exceeds" (with 's'), while ExitsRequestedExceedDemand uses "Exceed" (without 's'). Consider renaming to ExitsRequestedExceedsDemand for consistency.

📝 Suggested fix for naming consistency
-    error ExitsRequestedExceedDemand(uint256 requested, uint256 demand);
+    error ExitsRequestedExceedsDemand(uint256 requested, uint256 demand);

Note: This requires updating the corresponding revert statement in OperatorsRegistry.1.sol at line 496.

contracts/src/OperatorsRegistry.1.sol (1)

494-497: Demand check occurs after per-operator state mutations.

The loop at lines 468-492 modifies operator.requestedExits and emits RequestedValidatorExits events before the demand check at line 495. While the entire transaction reverts on failure (so state is consistent), emitted events during a reverted transaction can cause confusion in monitoring systems that process pending transactions.

This is a minor concern since reverted transactions don't persist events, but reordering the validation to happen before state mutations would be cleaner.

contracts/test/OperatorsRegistry.1.t.sol (1)

2717-2722: Consider moving event declarations to the test base class.

The events UpdatedRequestedValidatorExitsUponStopped and SetCurrentValidatorExitsDemand are declared within the OperatorsRegistryV1TestDistribution contract. Since these are also used in the main implementation, consider declaring them in OperatorsRegistryV1TestBase for reuse across test contracts.

Copy link
Contributor

@juliaaschmidt juliaaschmidt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM ! 🔥 Great job!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants