Skip to content

feat: add LI.FI SWAP validation, APPROVAL spender whitelist, and comprehensive tests for RocketPool validator#14

Open
ajag408 wants to merge 1 commit intoeng-1293-implement-rocketpool-shield-validatorfrom
feat/rocketpool-swap-validation-spender-whitelist
Open

feat: add LI.FI SWAP validation, APPROVAL spender whitelist, and comprehensive tests for RocketPool validator#14
ajag408 wants to merge 1 commit intoeng-1293-implement-rocketpool-shield-validatorfrom
feat/rocketpool-swap-validation-spender-whitelist

Conversation

@ajag408
Copy link
Contributor

@ajag408 ajag408 commented Mar 4, 2026

Summary

Stacked on #13. Adds the SWAP transaction type to the RocketPool Shield validator and tightens the APPROVAL flow with a spender whitelist. This completes the exit-path validation for ethereum-eth-reth-staking — the user's rETH unstake now has full coverage: APPROVAL → SWAP → funds received.

What changed

APPROVAL spender whitelist

PR #13 intentionally left the approve() spender unvalidated because it comes dynamically from the LI.FI /v1/quote API. On-chain investigation (Etherscan ERC20 transfer data for rETH) confirmed there are exactly two LI.FI contracts used as approval targets on Ethereum mainnet:

Contract Address Role
LI.FI Diamond 0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE Primary swap router
Permit2 Proxy 0x89c6340B1a1f4b25D36cd8B063D49045caF3f818 Secondary path via Uniswap Permit2, always forwards to Diamond

Both use the Diamond Proxy pattern (EIP-2535) — addresses are stable across upgrades. Approvals to any other spender are now blocked.

SWAP transaction validation (exit step 1)

Validates the LI.FI swap that converts rETH → ETH after the approval. Supports two call paths:

Direct Diamond callstx.to is the Diamond, tx.data is the swap calldata directly.

Permit2 Proxy wrapped callstx.to is the Permit2 Proxy, tx.data encodes one of three forwarding functions (callDiamondWithPermit2, callDiamondWithPermit2Witness, callDiamondWithEIP2612Signature) that embed the Diamond swap calldata as an inner bytes parameter. The validator extracts this inner calldata before validation.

Validation checks (in order)

# Check Blocks if
1 tx.from == userAddress Wrong sender
2 chainId == 1 Wrong network
3 tx.to in LIFI_CONTRACTS Unknown target contract
4 tx.value == 0 ETH value attached (rETH swaps are ERC20)
5 tx.data present and >= 10 chars Missing calldata
6 Extract Diamond calldata Permit2 Proxy outer function unparseable
7 Parse inner calldata against 7 whitelisted swap function signatures Unknown Diamond function selector
8 _receiver == userAddress Receiver mismatch — prevents fund redirection attacks

Check 8 is the critical security property. Without it, an attacker intercepting the LI.FI API response could modify _receiver to redirect the user's swap output through the legitimate Diamond contract. The _receiver parameter sits at args[3] across all 7 swap functions — verified by ABI analysis and confirmed stable across multiple versions of the LI.FI contract types.

Whitelisted Diamond swap functions

Function Variant
swapTokensGeneric Array of SwapData, payable
swapTokensSingleV3ERC20ToERC20 Single SwapData
swapTokensSingleV3ERC20ToNative Single SwapData
swapTokensSingleV3NativeToERC20 Single SwapData, payable
swapTokensMultipleV3ERC20ToERC20 Array of SwapData
swapTokensMultipleV3ERC20ToNative Array of SwapData
swapTokensMultipleV3NativeToERC20 Array of SwapData, payable

Any calldata with an unrecognized selector is blocked (fail-closed). These 7 signatures have been stable in the LI.FI Diamond ABI since 2023.

Files changed

File Change
rocketpool.validator.ts Added LIFI_CONTRACTS, LIFI_SWAP_ABI, PERMIT2_PROXY_ABI, spender whitelist in validateApproval, validateSwap, extractDiamondCalldata, validateLifiSwapReceiver
rocketpool.validator.test.ts Updated from 33 → 51 tests. Added 3 spender whitelist tests, 16 SWAP tests, 2 SWAP auto-detection tests. Inverted the "any spender" test to "reject unknown spender".

Test coverage (51 tests, all passing)

  • isSupported (1) — registry wiring
  • STAKE (15) — happy paths (legacy + EIP-1559), string chainId, wrong contract, wrong method, zero/missing value, wrong user, wrong network, appended bytes, invalid JSON, zero minTokensOut, zero idealTokensOut, inverted min/ideal
  • APPROVAL (12) — happy path, wrong contract, wrong method, ETH value, zero amount, wrong user, wrong network, appended bytes, max uint256, unknown spender rejected, Permit2 Proxy spender accepted, checksummed address accepted
  • SWAP (16) — 5 happy paths (Diamond generic, Diamond single V3, Permit2 callDiamondWithPermit2, callDiamondWithPermit2Witness, callDiamondWithEIP2612Signature), unknown contract, no calldata, unknown selector, wrong receiver, ETH value, wrong user, wrong network, Permit2 wrong inner receiver, Permit2 garbage inner calldata, Permit2 unparseable outer calldata
  • Auto-detection (6) — STAKE, APPROVAL, Diamond SWAP, Permit2 Proxy SWAP, unknown calldata, no ambiguous matches
  • General (3) — wrong user, wrong network, malformed JSON

Design notes

  • Fail-closed on unknown selectors. If LI.FI adds new swap facets, they'll be blocked until explicitly whitelisted. This is intentional — new functions could have different parameter layouts.

@coderabbitai
Copy link

coderabbitai bot commented Mar 4, 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.

Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6929efb4-cb41-46d1-b571-f7799bce186d

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:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/rocketpool-swap-validation-spender-whitelist

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

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