Skip to content
Closed
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
8c2d784
feat(byov): add allocation-based validator deposit with strict valida…
juliaaschmidt Feb 5, 2026
ca84d0e
chore: tidy up OperatorsRegistry tests for allocations
juliaaschmidt Feb 5, 2026
140ecb4
feat(deposits): add allocation ordering validation and optimize depos…
juliaaschmidt Feb 5, 2026
6f8430b
fix: revert NotEnoughFunds implementation changes + expectation of tests
juliaaschmidt Feb 5, 2026
c6ef4a6
style: consistent looping
juliaaschmidt Feb 5, 2026
8c0b55d
fix: natspec comment
juliaaschmidt Feb 5, 2026
9425654
test(deposits): add test for faulty registry returning fewer keys
juliaaschmidt Feb 5, 2026
7c20687
test(deposits): add test for allocation exceeding committed balance
juliaaschmidt Feb 5, 2026
69191ed
style: natspec changes
juliaaschmidt Feb 5, 2026
d286f80
chore: first draft
iamsahu Feb 5, 2026
a097680
fix: ConsensusLayerDepositManager tests
juliaaschmidt Feb 5, 2026
8ac80b1
style: forge fmt
juliaaschmidt Feb 5, 2026
4190d3e
test(deposits): add test for faulty registry returning more keys than…
juliaaschmidt Feb 5, 2026
79a945b
style: refactor allocation test helper to avoid duplication
juliaaschmidt Feb 5, 2026
618bd55
style: rm comment + rename helper for consistency
juliaaschmidt Feb 5, 2026
2577c48
feat: add more unit tests for pickNextValidatorsToDepositFromActiveOp…
juliaaschmidt Feb 5, 2026
5250225
chore: test fix
iamsahu Feb 5, 2026
486b172
test(consensus-layer-deposit): add allocation validation tests
juliaaschmidt Feb 5, 2026
23024db
test(operators-registry): add allocation validation error tests
juliaaschmidt Feb 5, 2026
cbb5293
style: forge fmt
juliaaschmidt Feb 5, 2026
2145d63
test(operators-registry): add test for zero fundable operators case
juliaaschmidt Feb 5, 2026
8e719c7
test(operators-registry): add branch coverage for operator lookup loop
juliaaschmidt Feb 5, 2026
dd689b5
feat: add another OR test to improve % coverage change
juliaaschmidt Feb 5, 2026
569e8d6
test(operators-registry): add coverage for inactive operator with mul…
juliaaschmidt Feb 5, 2026
179a9d7
review: test fix make more robust
juliaaschmidt Feb 5, 2026
8164b6d
style: rm duplicate test
juliaaschmidt Feb 5, 2026
fbc3cea
test(operators-registry): increase test coverage for OperatorsRegistr…
juliaaschmidt Feb 6, 2026
850e4a3
fix: forge fmt
juliaaschmidt Feb 6, 2026
cff3955
feat: add Operators.1.t.sol to increase cov
juliaaschmidt Feb 6, 2026
bb3d3f8
fix: unintended change
juliaaschmidt Feb 6, 2026
e3b06a3
chore: tests added for coverage
iamsahu Feb 6, 2026
a5836a0
chore: tests fixed
iamsahu Feb 6, 2026
74901b4
chore: coverage complete
iamsahu Feb 7, 2026
476c980
chore: test additions
iamsahu Feb 9, 2026
d7ec64f
chore: fix merge conflicts
iamsahu Feb 9, 2026
262a423
chore: fixed tests
iamsahu Feb 9, 2026
1474ee7
fix: Certora Prover Verifier Rules To Match New Function Signatures (…
juliaaschmidt Feb 9, 2026
5ed6327
Merge branch 'feat/byov-v1-clean' into feat/byov-v1-exit
iamsahu Feb 9, 2026
dbdc2b2
chore: test coverage
iamsahu Feb 9, 2026
9f56695
chore: removed unused code from OperatorsV2
iamsahu Feb 9, 2026
b20b8de
chore: check for 0 allocation
iamsahu Feb 9, 2026
1590ef2
chore: fix test
iamsahu Feb 9, 2026
00c3696
Merge branch 'main' into feat/byov-v1-clean
juliaaschmidt Feb 11, 2026
4128357
fix: update solc version Operators.1.t.sol
juliaaschmidt Feb 11, 2026
e21a489
bugfix: bump prover RedeemManagerV1.conf solc to 0.8.33
juliaaschmidt Feb 11, 2026
e374dec
bug-fix: add JDK version 21 to Certora.yaml
juliaaschmidt Feb 11, 2026
edfe6bb
bugfix: remove solc optimizer step
juliaaschmidt Feb 11, 2026
4b84520
chore: suggestions
iamsahu Feb 11, 2026
99653ea
Merge branch 'feat/byov-v1-clean' into feat/byov-v1-exit
iamsahu Feb 11, 2026
2d9ae64
chore: more suggestions
iamsahu Feb 11, 2026
a1278bb
chore: correction in error naming
iamsahu Feb 11, 2026
5c7e4b2
feat: external test helper (review suggestion)
juliaaschmidt Feb 11, 2026
292f26f
style: refactor test helpers to external Test Base (review suggestion)
juliaaschmidt Feb 11, 2026
8800197
chore: review suggestions
iamsahu Feb 11, 2026
75d6c4b
chore: review suggestions
iamsahu Feb 11, 2026
19c9f43
fix(OperatorsRegistry) prover fix
juliaaschmidt Feb 12, 2026
f03415a
chore: certora rule correction
iamsahu Feb 12, 2026
bfa5892
chore: certora rule correction
iamsahu Feb 12, 2026
ca28d0e
Merge branch 'feat/byov-v1-clean' into feat/byov-v1-exit
iamsahu Feb 12, 2026
9cf1690
chore: certora CI change
iamsahu Feb 13, 2026
24a9c2c
This adds in some more tests :
mischat Feb 16, 2026
9a108fb
This adds in some more tests :
mischat Feb 16, 2026
9d0ba9f
Merge branch 'main' into feat/byov-v1-exit
iamsahu Feb 17, 2026
bd14cbf
Merge branch 'feat/byov-v1-exit' of https://github.com/liquid-collect…
iamsahu Feb 17, 2026
0aa3f1d
chore: fix certora
iamsahu Feb 17, 2026
076fb98
chore: fix certora spec
iamsahu Feb 17, 2026
35cd977
chore: coverage increase
iamsahu Feb 17, 2026
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
6 changes: 6 additions & 0 deletions .github/workflows/Certora.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ jobs:
- name: Install certora
run: pip3 install certora-cli

- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'

- name: Install solc
run: |
pip install solc-select
Expand Down
2 changes: 1 addition & 1 deletion certora/conf/RedeemManagerV1.conf
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"loop_iter": "2",
"optimistic_loop": true,
"packages": ["openzeppelin-contracts=lib/openzeppelin-contracts"],
"solc": "solc8.20",
"solc": "solc8.33",
"prover_args": [
" -contractRecursionLimit 1", // River.resolveRedeemRequests(uint32[]) calls RedeemManager.resolveRedeemRequests(uint32[])
" -recursionErrorAsAssert false", //RedeemManager._claimRedeemRequest() is recursive
Expand Down
2 changes: 1 addition & 1 deletion certora/conf/RiverV1.conf
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"contracts/src/Allowlist.1.sol:AllowlistV1",
"contracts/src/CoverageFund.1.sol:CoverageFundV1",
"contracts/src/ELFeeRecipient.1.sol:ELFeeRecipientV1",
"contracts/src/OperatorsRegistry.1.sol:OperatorsRegistryV1",
"certora/harness/OperatorsRegistryV1Harness.sol:OperatorsRegistryV1Harness",
"certora/harness/RedeemManagerV1Harness.sol",
"contracts/src/Withdraw.1.sol:WithdrawV1",
"contracts/src/mock/DepositContractMock.sol", // This is needed only when working with the Ethereum network outside.
Expand Down
2 changes: 1 addition & 1 deletion certora/conf/SharesManagerV1.conf
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"contracts/src/Allowlist.1.sol:AllowlistV1",
"contracts/src/CoverageFund.1.sol:CoverageFundV1",
"contracts/src/ELFeeRecipient.1.sol:ELFeeRecipientV1",
"contracts/src/OperatorsRegistry.1.sol:OperatorsRegistryV1",
"certora/harness/OperatorsRegistryV1Harness.sol:OperatorsRegistryV1Harness",
"certora/harness/RedeemManagerV1Harness.sol",
"contracts/src/Withdraw.1.sol:WithdrawV1",
"contracts/src/mock/DepositContractMock.sol", // This is needed only when working with the Ethereum network outside.
Expand Down
6 changes: 1 addition & 5 deletions certora/harness/OperatorsRegistryV1Harness.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ contract OperatorsRegistryV1Harness is OperatorsRegistryV1 {
return OperatorsV2.getCount();
}

function getMaxValidatorAttributionPerRound() external view returns (uint256) {
return MAX_VALIDATOR_ATTRIBUTION_PER_ROUND;
}

function getStoppedValidatorsLength() external view returns (uint256) {
return OperatorsV2.getStoppedValidators().length;
}
Expand All @@ -42,7 +38,7 @@ contract OperatorsRegistryV1Harness is OperatorsRegistryV1 {
OperatorsV2.Operator memory op = OperatorsV2.get(opIndex);
uint256 valIndex = 0;
bytes32 validatorDataHash = keccak256(abi.encodePacked(publicKeyAndSignature));
for (; valIndex < op.keys;)
for (; valIndex < op.keys; ++valIndex)
{
(bytes memory valData) = ValidatorKeys.getRaw(opIndex, valIndex);
if (validatorDataHash == keccak256(abi.encodePacked(valData))) //element found
Expand Down
4 changes: 2 additions & 2 deletions certora/specs/Base.spec
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ methods {
function RiverV1Harness.getCLValidatorCount() external returns(uint256) envfree;

// RiverV1 : ConsensusLayerDepositManagerV1
function _.depositToConsensusLayerWithDepositRoot(uint256, bytes32) external => DISPATCHER(true);
function _.depositToConsensusLayerWithDepositRoot(IOperatorsRegistryV1.OperatorAllocation[], bytes32) external => DISPATCHER(true);
function RiverV1Harness.getDepositedValidatorCount() external returns(uint256) envfree;

// WithdrawV1
Expand All @@ -83,7 +83,7 @@ methods {
function OR.getStoppedAndRequestedExitCounts() external returns (uint32, uint256) envfree;
function _.getStoppedAndRequestedExitCounts() external => DISPATCHER(true);
function _.demandValidatorExits(uint256, uint256) external => DISPATCHER(true);
function _.pickNextValidatorsToDeposit(uint256) external => DISPATCHER(true); // has no effect - CERT-4615
function _.pickNextValidatorsToDeposit(IOperatorsRegistryV1.OperatorAllocation[]) external => DISPATCHER(true); // has no effect - CERT-4615

//function _.deposit(bytes,bytes,bytes,bytes32) external => DISPATCHER(true); // has no effect - CERT-4615

Expand Down
6 changes: 3 additions & 3 deletions certora/specs/OperatorRegistryV1.spec
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ invariant inactiveOperatorsRemainNotFunded_LI2(uint opIndex)
}
{
preserved requestValidatorExits(uint256 x) with(env e) { require x <= 2; }
preserved pickNextValidatorsToDeposit(uint256 x) with(env e) { require x <= 1; }
preserved pickNextValidatorsToDeposit(IOperatorsRegistryV1.OperatorAllocation[] x) with(env e) { require x.length <= 1; }
preserved removeValidators(uint256 _index, uint256[] _indexes) with(env e) { require _indexes.length <= 1; }
}

Expand Down Expand Up @@ -216,7 +216,7 @@ invariant operatorsStatesRemainValid_LI2_easyMethods(uint opIndex)
filtered { f -> !ignoredMethod(f) &&
!needsLoopIter4(f) &&
f.selector != sig:requestValidatorExits(uint256).selector &&
f.selector != sig:pickNextValidatorsToDeposit(uint256).selector &&
f.selector != sig:pickNextValidatorsToDeposit(IOperatorsRegistryV1.OperatorAllocation[]).selector &&
f.selector != sig:removeValidators(uint256,uint256[]).selector
}

Expand All @@ -225,7 +225,7 @@ invariant operatorsStatesRemainValid_LI2_easyMethods(uint opIndex)
invariant operatorsStatesRemainValid_LI2_pickNextValidatorsToDeposit(uint opIndex)
isValidState() => (operatorStateIsValid(opIndex))
filtered { f -> !ignoredMethod(f) &&
!needsLoopIter4(f) && f.selector != sig:pickNextValidatorsToDeposit(uint256).selector
!needsLoopIter4(f) && f.selector != sig:pickNextValidatorsToDeposit(IOperatorsRegistryV1.OperatorAllocation[]).selector
}

// proves the invariant for reportStoppedValidatorCounts
Expand Down
6 changes: 3 additions & 3 deletions certora/specs/OperatorRegistryV1_base.spec
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ methods {
function OR.getStoppedAndRequestedExitCounts() external returns (uint32, uint256) envfree;
function _.getStoppedAndRequestedExitCounts() external => DISPATCHER(true);
function _.demandValidatorExits(uint256, uint256) external => DISPATCHER(true);
//function _.pickNextValidatorsToDeposit(uint256) external => DISPATCHER(true); // has no effect - CERT-4615
//function _.pickNextValidatorsToDeposit(IOperatorsRegistryV1.OperatorAllocation[]) external => DISPATCHER(true); // has no effect - CERT-4615

//function _.deposit(bytes,bytes,bytes,bytes32) external => DISPATCHER(true); // has no effect - CERT-4615

Expand All @@ -30,7 +30,7 @@ methods {
function OR.getActiveOperatorsCount() external returns (uint256) envfree;
function OR.getOperatorsSaturationDiscrepancy() external returns (uint256) envfree;
function OR.getKeysCount(uint256) external returns (uint256) envfree;
function OR.pickNextValidatorsToDeposit(uint256) external returns (bytes[] memory, bytes[] memory);
function OR.pickNextValidatorsToDeposit(IOperatorsRegistryV1.OperatorAllocation[]) external returns (bytes[] memory, bytes[] memory);
function OR.requestValidatorExits(uint256) external;
function OR.setOperatorAddress(uint256, address) external;
function OR.getOperatorsSaturationDiscrepancy(uint256, uint256) external returns (uint256) envfree;
Expand Down Expand Up @@ -88,7 +88,7 @@ definition isMethodID(method f, uint ID) returns bool =
(f.selector == sig:addOperator(string,address).selector && ID == 4) ||
(f.selector == sig:demandValidatorExits(uint256,uint256).selector && ID == 5) ||
(f.selector == sig:initOperatorsRegistryV1(address,address).selector && ID == 6) ||
(f.selector == sig:pickNextValidatorsToDeposit(uint256).selector && ID == 7) ||
(f.selector == sig:pickNextValidatorsToDeposit(IOperatorsRegistryV1.OperatorAllocation[]).selector && ID == 7) ||
(f.selector == sig:proposeAdmin(address).selector && ID == 8) ||
(f.selector == sig:removeValidators(uint256,uint256[]).selector && ID == 9) ||
(f.selector == sig:requestValidatorExits(uint256).selector && ID == 10) ||
Expand Down
8 changes: 4 additions & 4 deletions certora/specs/OperatorRegistryV1_finishedRules.spec
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ invariant inactiveOperatorsRemainNotFunded(uint opIndex)
(!getOperator(opIndex).active => getOperator(opIndex).funded == 0)
{
preserved requestValidatorExits(uint256 x) with(env e) { require x <= 2; }
preserved pickNextValidatorsToDeposit(uint256 x) with(env e) { require x <= 1; }
preserved pickNextValidatorsToDeposit(IOperatorsRegistryV1.OperatorAllocation[] x) with(env e) { require x.length <= 1; }
preserved removeValidators(uint256 _index, uint256[] _indexes) with(env e) { require _indexes.length <= 1; }
}

Expand All @@ -68,7 +68,7 @@ invariant inactiveOperatorsRemainNotFunded_LI2(uint opIndex)
}
{
preserved requestValidatorExits(uint256 x) with(env e) { require x <= 2; }
preserved pickNextValidatorsToDeposit(uint256 x) with(env e) { require x <= 1; }
preserved pickNextValidatorsToDeposit(IOperatorsRegistryV1.OperatorAllocation[] x) with(env e) { require x.length <= 1; }
preserved removeValidators(uint256 _index, uint256[] _indexes) with(env e) { require _indexes.length <= 1; }
}

Expand Down Expand Up @@ -181,7 +181,7 @@ invariant operatorsStatesRemainValid_LI2_easyMethods(uint opIndex)
filtered { f -> !ignoredMethod(f) &&
!needsLoopIter4(f) &&
f.selector != sig:requestValidatorExits(uint256).selector &&
f.selector != sig:pickNextValidatorsToDeposit(uint256).selector &&
f.selector != sig:pickNextValidatorsToDeposit(IOperatorsRegistryV1.OperatorAllocation[]).selector &&
f.selector != sig:removeValidators(uint256,uint256[]).selector
}

Expand All @@ -190,7 +190,7 @@ invariant operatorsStatesRemainValid_LI2_easyMethods(uint opIndex)
invariant operatorsStatesRemainValid_LI2_pickNextValidatorsToDeposit(uint opIndex)
isValidState() => (operatorStateIsValid(opIndex))
filtered { f -> !ignoredMethod(f) &&
!needsLoopIter4(f) && f.selector != sig:pickNextValidatorsToDeposit(uint256).selector
!needsLoopIter4(f) && f.selector != sig:pickNextValidatorsToDeposit(IOperatorsRegistryV1.OperatorAllocation[]).selector
}

// proves the invariant for reportStoppedValidatorCounts
Expand Down
16 changes: 8 additions & 8 deletions certora/specs/OperatorRegistryV1_orig.spec
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ invariant validatorKeysRemainUnique_LI2(
filtered { f -> !ignoredMethod(f) && !needsLoopIter4(f) }
{
preserved requestValidatorExits(uint256 x) with(env e) { require x <= 2; }
preserved pickNextValidatorsToDeposit(uint256 x) with(env e) { require x <= 2; }
preserved pickNextValidatorsToDeposit(IOperatorsRegistryV1.OperatorAllocation[] x) with(env e) { require x.length <= 2; }
preserved removeValidators(uint256 _index, uint256[] _indexes) with(env e) { require _indexes.length <= 2; }
}

Expand Down Expand Up @@ -73,9 +73,9 @@ rule startingValidatorsDecreasesDiscrepancy(env e)
require getKeysCount(index1) < 5;
require getKeysCount(index2) < 5; //counterexamples when the keys count overflows

uint count;
require count > 0 && count <= 3;
pickNextValidatorsToDeposit(e, count);
IOperatorsRegistryV1.OperatorAllocation[] allocations;
require allocations.length > 0 && allocations.length <= 3;
pickNextValidatorsToDeposit(e, allocations);
uint discrepancyAfter = getOperatorsSaturationDiscrepancy(index1, index2);

//uint256 keysAfter1; uint256 limitAfter1; uint256 fundedAfter1; uint256 requestedExitsAfter1; uint256 stoppedCountAfter1; bool activeAfter1; address operatorAfter1;
Expand All @@ -84,7 +84,7 @@ rule startingValidatorsDecreasesDiscrepancy(env e)
//keysAfter2, limitAfter2, fundedAfter2, requestedExitsAfter2, stoppedCountAfter2, activeAfter2, operatorAfter2 = getOperatorState(e, index2);

assert discrepancyBefore > 0 => to_mathint(discrepancyBefore) >=
discrepancyAfter - count + 1; //getMaxValidatorAttributionPerRound(e);
discrepancyAfter - allocations.length + 1;
}

rule startingValidatorsNeverUsesSameValidatorTwice(env e)
Expand Down Expand Up @@ -129,9 +129,9 @@ rule witness4_3StartingValidatorsDecreasesDiscrepancy(env e)
require operatorStateIsValid(index2);

uint discrepancyBefore = getOperatorsSaturationDiscrepancy(index1, index2);
uint count;
require count <= 1;
pickNextValidatorsToDeposit(e, count);
IOperatorsRegistryV1.OperatorAllocation[] allocations;
require allocations.length <= 1;
pickNextValidatorsToDeposit(e, allocations);
uint discrepancyAfter = getOperatorsSaturationDiscrepancy(index1, index2);
satisfy discrepancyBefore == 4 && discrepancyAfter == 3;
}
Expand Down
2 changes: 1 addition & 1 deletion certora/specs/SharesManagerV1.spec
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ rule sharesBalanceChangesRestrictively(method f) filtered {
rule pricePerShareChangesRespectively(method f) filtered {
f -> !f.isView
&& f.selector != sig:initRiverV1_1(address,uint64,uint64,uint64,uint64,uint64,uint256,uint256,uint128,uint128).selector
&& f.selector != sig:depositToConsensusLayerWithDepositRoot(uint256, bytes32).selector
&& f.selector != sig:depositToConsensusLayerWithDepositRoot(IOperatorsRegistryV1.OperatorAllocation[], bytes32).selector
&& f.selector != sig:claimRedeemRequests(uint32[],uint32[]).selector
&& f.selector != sig:deposit().selector
&& f.selector != sig:depositAndTransfer(address).selector
Expand Down
24 changes: 12 additions & 12 deletions certora/specs_for_CI/OperatorRegistryV1_for_CI_3.spec
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ rule startingValidatorsDecreasesDiscrepancy(env e)
require getKeysCount(index1) < 5;
require getKeysCount(index2) < 5;

uint count;
require count > 0 && count <= 3;
pickNextValidatorsToDeposit(e, count);
IOperatorsRegistryV1.OperatorAllocation[] allocations;
require allocations.length > 0 && allocations.length <= 3;
pickNextValidatorsToDeposit(e, allocations);
uint discrepancyAfter = getOperatorsSaturationDiscrepancy(index1, index2);

assert discrepancyBefore > 0 => to_mathint(discrepancyBefore) >=
discrepancyAfter - count + 1; // this conditions is fine as long as count <= MAX_VALIDATOR_ATTRIBUTION_PER_ROUND
discrepancyAfter - allocations.length + 1;
}

rule witness4_3StartingValidatorsDecreasesDiscrepancy(env e)
Expand All @@ -38,9 +38,9 @@ rule witness4_3StartingValidatorsDecreasesDiscrepancy(env e)
require operatorStateIsValid(index2);

uint discrepancyBefore = getOperatorsSaturationDiscrepancy(index1, index2);
uint count;
require count <= 1;
pickNextValidatorsToDeposit(e, count);
IOperatorsRegistryV1.OperatorAllocation[] allocations;
require allocations.length <= 1;
pickNextValidatorsToDeposit(e, allocations);
uint discrepancyAfter = getOperatorsSaturationDiscrepancy(index1, index2);
satisfy discrepancyBefore == 4 && discrepancyAfter == 3;
}
Expand Down Expand Up @@ -119,7 +119,7 @@ invariant inactiveOperatorsRemainNotFunded_LI2(uint opIndex)
}
{
preserved requestValidatorExits(uint256 x) with(env e) { require x <= 2; }
preserved pickNextValidatorsToDeposit(uint256 x) with(env e) { require x <= 1; }
preserved pickNextValidatorsToDeposit(IOperatorsRegistryV1.OperatorAllocation[] x) with(env e) { require x.length <= 1; }
Comment on lines +122 to 123
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Preserved requestValidatorExits should reflect the non‑empty precondition.

The implementation rejects empty allocations; preserved blocks that allow length 0 can model impossible calls and weaken the invariants.

🛠️ Suggested fix (apply to all preserved `requestValidatorExits` blocks)
-        preserved requestValidatorExits(IOperatorsRegistryV1.OperatorAllocation[] x) with(env e) { require x.length <= 2; }
+        preserved requestValidatorExits(IOperatorsRegistryV1.OperatorAllocation[] x) with(env e) { require x.length > 0 && x.length <= 2; }

Also applies to: 206-207, 213-214, 395-396, 787-788

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@certora/specs_for_CI/OperatorRegistryV1_for_CI_3.spec` around lines 122 -
123, The preserved clauses allow empty allocations but the implementation
rejects empty inputs; update each preserved
`requestValidatorExits(IOperatorsRegistryV1.OperatorAllocation[] x)` to include
a non‑empty precondition (e.g., require x.length >= 1 && x.length <= 2) so the
spec matches implementation, and likewise tighten
`pickNextValidatorsToDeposit(IOperatorsRegistryV1.OperatorAllocation[] x)` to
require at least one element (e.g., require x.length >= 1 && x.length <= 1 or
require x.length == 1); apply the same change to the other preserved blocks
mentioned (the repeated `requestValidatorExits` preserved blocks).

preserved removeValidators(uint256 _index, uint256[] _indexes) with(env e) { require _indexes.length <= 1; }
}

Expand Down Expand Up @@ -178,7 +178,7 @@ invariant operatorsStatesRemainValid_LI2_easyMethods(uint opIndex)
filtered { f -> !ignoredMethod(f) &&
!needsLoopIter4(f) &&
f.selector != sig:requestValidatorExits(uint256).selector &&
f.selector != sig:pickNextValidatorsToDeposit(uint256).selector &&
f.selector != sig:pickNextValidatorsToDeposit(IOperatorsRegistryV1.OperatorAllocation[]).selector &&
f.selector != sig:removeValidators(uint256,uint256[]).selector
}

Expand All @@ -187,7 +187,7 @@ invariant operatorsStatesRemainValid_LI2_easyMethods(uint opIndex)
invariant operatorsStatesRemainValid_LI2_pickNextValidatorsToDeposit(uint opIndex)
isValidState() => (operatorStateIsValid(opIndex))
filtered { f -> !ignoredMethod(f) &&
!needsLoopIter4(f) && f.selector != sig:pickNextValidatorsToDeposit(uint256).selector
!needsLoopIter4(f) && f.selector != sig:pickNextValidatorsToDeposit(IOperatorsRegistryV1.OperatorAllocation[]).selector
}

// proves the invariant for reportStoppedValidatorCounts
Expand Down Expand Up @@ -392,7 +392,7 @@ invariant validatorKeysRemainUnique_LI2(
filtered { f -> !ignoredMethod(f) && !needsLoopIter4(f) }
{
preserved requestValidatorExits(uint256 x) with(env e) { require x <= 2; }
preserved pickNextValidatorsToDeposit(uint256 x) with(env e) { require x <= 2; }
preserved pickNextValidatorsToDeposit(IOperatorsRegistryV1.OperatorAllocation[] x) with(env e) { require x.length <= 2; }
preserved removeValidators(uint256 _index, uint256[] _indexes) with(env e) { require _indexes.length <= 2; }
}

Expand Down Expand Up @@ -784,7 +784,7 @@ invariant inactiveOperatorsRemainNotFunded(uint opIndex)
(!getOperator(opIndex).active => getOperator(opIndex).funded == 0)
{
preserved requestValidatorExits(uint256 x) with(env e) { require x <= 2; }
preserved pickNextValidatorsToDeposit(uint256 x) with(env e) { require x <= 1; }
preserved pickNextValidatorsToDeposit(IOperatorsRegistryV1.OperatorAllocation[] x) with(env e) { require x.length <= 1; }
preserved removeValidators(uint256 _index, uint256[] _indexes) with(env e) { require _indexes.length <= 1; }
}

Expand Down
2 changes: 1 addition & 1 deletion certora/specs_for_CI/River_base.spec
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ definition ignoredMethod(method f) returns bool =
f.selector == sig:helper11_commitBalanceToDeposit(OracleManagerV1.ConsensusLayerDataReportingVariables).selector;

definition excludedInCI(method f) returns bool =
f.selector == sig:depositToConsensusLayerWithDepositRoot(uint256, bytes32).selector;
f.selector == sig:depositToConsensusLayerWithDepositRoot(IOperatorsRegistryV1.OperatorAllocation[], bytes32).selector;
Loading