Skip to content

[Do Not Merge: CI BLOCKED ON PR #20] feat: add slippage floor validation for RocketPool STAKE and SWAP (XBTO request) #22

Open
ajag408 wants to merge 2 commits into
mainfrom
feat/eng-2460-rocketpool-slippage-floor
Open

[Do Not Merge: CI BLOCKED ON PR #20] feat: add slippage floor validation for RocketPool STAKE and SWAP (XBTO request) #22
ajag408 wants to merge 2 commits into
mainfrom
feat/eng-2460-rocketpool-slippage-floor

Conversation

@ajag408
Copy link
Copy Markdown
Contributor

@ajag408 ajag408 commented May 16, 2026

Summary by CodeRabbit

  • Security Improvements

    • Added slippage-floor protection for staking and swap transactions to prevent execution below acceptable thresholds.
    • Enhanced validation to reject transactions with manipulated minimum output amounts.
  • Tests

    • Significantly expanded test coverage for slippage-floor boundary scenarios and swap validation cases.

Review Change Stack

  • Adds input-relative slippage floor (80%) for minTokensOut on STAKE and _minAmountOut on SWAP in the RocketPool validator
  • Previously, Shield only checked minTokensOut > 0 (STAKE) and validated _receiver (SWAP) — a compromised upstream could set minTokensOut = 1 wei on a 1 ETH stake and pass validation, exposing the transaction to MEV sandwich extraction
  • The 80% floor is conservative — rETH has never traded below 0.90 ETH/rETH — no risk of blocking legitimate transactions
  • Self-contained in the RocketPool validator: no API changes, no monorepo impact, no cross-cutting dependencies

What changed

File Change
src/validators/evm/rocketpool/rocketpool.validator.ts Added SLIPPAGE_FLOOR_BPS constant, slippage check in validateStake, _minAmountOut > 0 + slippage check in validateLifiSwapReceiver (with Single/Multiple variant handling)
src/validators/evm/rocketpool/rocketpool.validator.test.ts Added 16 new tests: STAKE slippage boundary tests, SWAP slippage tests (zero, manipulated, boundary), Multiple variant coverage (happy path, receiver, Permit2 wrapping, slippage), multi-entry swapData summing

Security context

Addresses the slippage parameter manipulation gap identified in the Shield security review. Unlike the args.amount validation (Phase 6), this fix is independently shippable because both the input (tx.value / fromAmount) and output floor (minTokensOut / _minAmountOut) are already present in the transaction calldata.

Threat model: requires a compromised upstream API (consistent with Shield's threat model). The validator remains fail-closed — unknown LI.FI function selectors are blocked, not bypassed.

Test plan

  • All 67 RocketPool tests pass (59 existing + 8 new slippage + 8 new Multiple variant + 1 new general)
  • Existing valid transactions unaffected (use 90% slippage, well above 80% floor)
  • Manipulated slippage (1 wei) blocked for both STAKE and SWAP
  • Zero _minAmountOut blocked on SWAP
  • Boundary tests: exactly-at-80% passes, 80%-minus-1-wei blocked
  • Permit2-wrapped SWAP with manipulated slippage blocked (all 3 proxy methods)
  • Multiple variant coverage: happy path, wrong receiver, multi-entry fromAmount summing, slippage floor

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 16, 2026

📝 Walkthrough

Walkthrough

This PR adds slippage-floor validation to the RocketPoolValidator for both RocketPool staking transactions and LI.FI swap operations. An 80% floor is enforced on staking (minTokensOut must not fall below 80% of ETH value), and on swaps (minAmountOut must not fall below 80% of fromAmount, with logic to sum fromAmount across Multiple swap entries). Test coverage includes boundary cases, happy paths, receiver validation, and comprehensive slippage-floor scenarios.

Changes

Slippage Floor Validation for RocketPool & LI.FI

Layer / File(s) Summary
Slippage floor constant and RocketPool staking validation
src/validators/evm/rocketpool/rocketpool.validator.ts (lines 27–28, 153–166)
Module-level SLIPPAGE_FLOOR_BPS constant (80%) is added and used in validateStake to compute a minimum acceptable minTokensOut as a fraction of the ETH value sent, rejecting swapTo calls that fall below the floor.
LI.FI swap slippage validation with fromAmount extraction
src/validators/evm/rocketpool/rocketpool.validator.ts (lines 295–325)
Extends LI.FI swap validation to require _minAmountOut > 0 and derive total fromAmount from parsed calldata—summing across "Multiple" swap tuples or using the single tuple—then blocks swaps when _minAmountOut is below the slippage floor relative to that fromAmount.
Test ABI expansion and RocketPool staking slippage tests
src/validators/evm/rocketpool/rocketpool.validator.test.ts (lines 38–39, 499–588)
LI.FI ABI is updated to include swapTokensMultipleV3ERC20ToERC20 and swapTokensMultipleV3ERC20ToNative signatures. STAKE slippage-floor tests validate rejection of far-below-floor minTokensOut, acceptance at exactly 80% of tx.value, and rejection just below that boundary.
SWAP happy-path, receiver validation, and slippage floor tests
src/validators/evm/rocketpool/rocketpool.validator.test.ts (lines 970–1033, 1103–1181, 1297–1334, 1503–1811)
Comprehensive SWAP test coverage: happy-path tests for direct Diamond and Permit2-wrapped Multiple variants with native/ERC20 output; receiver-mismatch rejection test; slippage-floor boundary tests covering manipulated/zero _minAmountOut, exact 80% threshold, sub-threshold rejection, Permit2-wrapped scenarios, and validation that slippage checks correctly sum fromAmount across multiple swapData entries.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • stakekit/shield#13: Both PRs modify the same RocketPoolValidator implementation and its test suite (the earlier PR introduces the initial STAKE/APPROVAL validator; this PR extends that validator with slippage-floor logic and expanded SWAP-specific validation).

Suggested reviewers

  • raiseerco
  • aditya172926
  • jdomingos
  • Philippoes

Poem

🐰 A rabbit hops with careful stride,
Ensuring slips don't slip too wide—
Eighty percent, a steady floor,
For stakes and swaps worth keeping for.
No shoddy deals shall pass on by!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding slippage floor validation for RocketPool STAKE and SWAP. It is specific and directly related to the changeset. The '[Do Not Merge: CI BLOCKED ON PR #20]' prefix indicates a temporary status marker but does not obscure the actual feature description.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/eng-2460-rocketpool-slippage-floor

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

Copy link
Copy Markdown

@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.

🧹 Nitpick comments (6)
src/validators/evm/rocketpool/rocketpool.validator.ts (1)

312-324: ⚡ Quick win

Consider blocking swaps with zero fromAmount.

When totalFromAmount === 0n, the slippage floor check is skipped entirely. While mathematically this is equivalent to a floor of 0, a legitimate swap should always have a positive input amount. Blocking this case maintains the fail-closed security posture.

♻️ Proposed fix to block zero fromAmount
+    if (totalFromAmount <= 0n) {
+      return this.blocked('fromAmount must be greater than zero');
+    }
-    if (totalFromAmount > 0n) {
-      const slippageFloor = (totalFromAmount * SLIPPAGE_FLOOR_BPS) / 10000n;
-      if (minAmountOut < slippageFloor) {
-        return this.blocked(
-          '_minAmountOut is too low relative to fromAmount (slippage floor)',
-          {
-            minAmountOut: minAmountOut.toString(),
-            fromAmount: totalFromAmount.toString(),
-            slippageFloor: slippageFloor.toString(),
-          },
-        );
-      }
+    const slippageFloor = (totalFromAmount * SLIPPAGE_FLOOR_BPS) / 10000n;
+    if (minAmountOut < slippageFloor) {
+      return this.blocked(
+        '_minAmountOut is too low relative to fromAmount (slippage floor)',
+        {
+          minAmountOut: minAmountOut.toString(),
+          fromAmount: totalFromAmount.toString(),
+          slippageFloor: slippageFloor.toString(),
+        },
+      );
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/validators/evm/rocketpool/rocketpool.validator.ts` around lines 312 -
324, The check currently skips when totalFromAmount === 0n; instead enforce that
totalFromAmount must be > 0n by adding a guard before the slippage-floor logic:
if totalFromAmount === 0n return this.blocked(...) with a clear message (e.g.,
"_fromAmount must be > 0") and include totalFromAmount and minAmountOut in the
metadata; keep the existing slippage-floor check using SLIPPAGE_FLOOR_BPS and
minAmountOut unchanged so the zero-case is explicitly rejected.
src/validators/evm/rocketpool/rocketpool.validator.test.ts (5)

1115-1120: 💤 Low value

Fix code formatting.

The code has Prettier formatting violations that should be corrected.

🎨 Proposed formatting fix
-      const permit2WrappedMultiple =
-        permit2ProxyIface.encodeFunctionData('callDiamondWithPermit2', [
-          multipleSwapCalldata,
-          dummyPermit,
-          dummySignature,
-        ]);
+      const permit2WrappedMultiple = permit2ProxyIface.encodeFunctionData(
+        'callDiamondWithPermit2',
+        [multipleSwapCalldata, dummyPermit, dummySignature]
+      );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/validators/evm/rocketpool/rocketpool.validator.test.ts` around lines 1115
- 1120, The line assigning permit2WrappedMultiple is misformatted; reformat the
call to permit2ProxyIface.encodeFunctionData('callDiamondWithPermit2',
[multipleSwapCalldata, dummyPermit, dummySignature]) to match project Prettier
rules (proper indentation and line breaks) so the array arguments are each on
their own properly indented line and the overall statement follows surrounding
test formatting conventions; update the statement that defines
permit2WrappedMultiple accordingly.

1659-1664: 💤 Low value

Fix code formatting.

The code has Prettier formatting violations that should be corrected.

🎨 Proposed formatting fix
-      const permit2WrappedManipulated =
-        permit2ProxyIface.encodeFunctionData('callDiamondWithPermit2', [
-          manipulatedDiamondCalldata,
-          dummyPermit,
-          dummySignature,
-        ]);
+      const permit2WrappedManipulated = permit2ProxyIface.encodeFunctionData(
+        'callDiamondWithPermit2',
+        [manipulatedDiamondCalldata, dummyPermit, dummySignature]
+      );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/validators/evm/rocketpool/rocketpool.validator.test.ts` around lines 1659
- 1664, The test contains Prettier formatting violations around the Permit2 call
encoding; reformat the expression that assigns permit2WrappedManipulated so it
complies with project Prettier rules (proper line breaks and indentation) for
the call to permit2ProxyIface.encodeFunctionData('callDiamondWithPermit2',
[manipulatedDiamondCalldata, dummyPermit, dummySignature]); locate the
permit2WrappedManipulated assignment and reflow the arguments (or inline them)
and spacing to match surrounding tests' style so the file passes Prettier.

1688-1688: 💤 Low value

Remove trailing whitespace.

Line has unnecessary trailing whitespace.

🎨 Proposed fix
       expect(swapAttempt?.reason).toContain('slippage floor');
     });
-    
+
     it('should reject a Multiple variant swap with manipulated slippage', () => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/validators/evm/rocketpool/rocketpool.validator.test.ts` at line 1688,
Remove the trailing whitespace on the indicated line in
src/validators/evm/rocketpool/rocketpool.validator.test.ts (line ~1688) by
deleting the extra space at the end of the line; you can also run the repo
formatter or ESLint fix to ensure no other trailing whitespace remains and
commit the cleaned file (target the test file name rocketpool.validator.test.ts
to locate the offending line).

1297-1297: 💤 Low value

Remove trailing whitespace.

Line has unnecessary trailing whitespace.

🎨 Proposed fix
-    });
-  
+    });
+
     it('should reject Multiple variant swap with wrong receiver', () => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/validators/evm/rocketpool/rocketpool.validator.test.ts` at line 1297,
Remove the trailing whitespace on the reported empty line in
rocketpool.validator.test.ts by deleting the extra space characters at the end
of the line (or run your formatter/prettier to trim end-of-line whitespace),
then re-run the tests/linter to ensure no trailing-space warnings remain.

1610-1610: 💤 Low value

Remove trailing whitespace.

Line has unnecessary trailing whitespace.

🎨 Proposed fix
       expect(result.detectedType).toBe(TransactionType.SWAP);
     });
-  
+
     it('should reject SWAP with _minAmountOut just below 80% floor', () => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/validators/evm/rocketpool/rocketpool.validator.test.ts` at line 1610,
Remove the trailing whitespace on the flagged line in
rocketpool.validator.test.ts: open the test file (rocketpool.validator.test.ts),
find the empty/whitespace-only characters at the end of the reported line and
delete them so the line ends with no trailing spaces, then save and run
linters/tests to confirm no trailing-whitespace errors remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/validators/evm/rocketpool/rocketpool.validator.test.ts`:
- Around line 1115-1120: The line assigning permit2WrappedMultiple is
misformatted; reformat the call to
permit2ProxyIface.encodeFunctionData('callDiamondWithPermit2',
[multipleSwapCalldata, dummyPermit, dummySignature]) to match project Prettier
rules (proper indentation and line breaks) so the array arguments are each on
their own properly indented line and the overall statement follows surrounding
test formatting conventions; update the statement that defines
permit2WrappedMultiple accordingly.
- Around line 1659-1664: The test contains Prettier formatting violations around
the Permit2 call encoding; reformat the expression that assigns
permit2WrappedManipulated so it complies with project Prettier rules (proper
line breaks and indentation) for the call to
permit2ProxyIface.encodeFunctionData('callDiamondWithPermit2',
[manipulatedDiamondCalldata, dummyPermit, dummySignature]); locate the
permit2WrappedManipulated assignment and reflow the arguments (or inline them)
and spacing to match surrounding tests' style so the file passes Prettier.
- Line 1688: Remove the trailing whitespace on the indicated line in
src/validators/evm/rocketpool/rocketpool.validator.test.ts (line ~1688) by
deleting the extra space at the end of the line; you can also run the repo
formatter or ESLint fix to ensure no other trailing whitespace remains and
commit the cleaned file (target the test file name rocketpool.validator.test.ts
to locate the offending line).
- Line 1297: Remove the trailing whitespace on the reported empty line in
rocketpool.validator.test.ts by deleting the extra space characters at the end
of the line (or run your formatter/prettier to trim end-of-line whitespace),
then re-run the tests/linter to ensure no trailing-space warnings remain.
- Line 1610: Remove the trailing whitespace on the flagged line in
rocketpool.validator.test.ts: open the test file (rocketpool.validator.test.ts),
find the empty/whitespace-only characters at the end of the reported line and
delete them so the line ends with no trailing spaces, then save and run
linters/tests to confirm no trailing-whitespace errors remain.

In `@src/validators/evm/rocketpool/rocketpool.validator.ts`:
- Around line 312-324: The check currently skips when totalFromAmount === 0n;
instead enforce that totalFromAmount must be > 0n by adding a guard before the
slippage-floor logic: if totalFromAmount === 0n return this.blocked(...) with a
clear message (e.g., "_fromAmount must be > 0") and include totalFromAmount and
minAmountOut in the metadata; keep the existing slippage-floor check using
SLIPPAGE_FLOOR_BPS and minAmountOut unchanged so the zero-case is explicitly
rejected.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 270d8592-7b23-4465-a6cb-a52c0dab2333

📥 Commits

Reviewing files that changed from the base of the PR and between 9b8f7c3 and dfd3ffe.

📒 Files selected for processing (2)
  • src/validators/evm/rocketpool/rocketpool.validator.test.ts
  • src/validators/evm/rocketpool/rocketpool.validator.ts

@ajag408 ajag408 changed the title feat: add slippage floor validation for RocketPool STAKE and SWAP feat: add slippage floor validation for RocketPool STAKE and SWAP (XBTO request) May 16, 2026
@ajag408
Copy link
Copy Markdown
Contributor Author

ajag408 commented May 16, 2026

**Blocked on #20 **: This PR requires the CI hardening PR to merge first — needed for CI workflows to run on this branch.

@ajag408 ajag408 changed the title feat: add slippage floor validation for RocketPool STAKE and SWAP (XBTO request) [Do Not Merge: CI BLOCKED ON PR #20] feat: add slippage floor validation for RocketPool STAKE and SWAP (XBTO request) May 16, 2026
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.

1 participant