ethereum-optimism / ethereum-optimism/actions
Consolidate validation and lifecycle abstractions across action providers
- Dominant language
- TypeScript
- Stars
- 32
- Forks
- 25
- Avg merge
- 10h 20m
- Merged PRs (30d)
- 16
Description
## Context
After the borrow PR #3 review consolidation, the three action provider domains (`LendProvider`, `SwapProvider`, `BorrowProvider`) have drifted in how they shape per-action validation, allowlist resolution, and config matching. Borrow is now the most refined (post-`hoist allowlist resolution` commit); lend and swap haven't been brought up to parity.
This issue tracks the larger pass: pull shared logic up into `BaseActionProvider` where it belongs, extract per-domain `validations.ts` modules, and make naming/shape parallel across all three domains.
## Current state
| Concern | Lend | Swap | Borrow (post-cleanup) |
|---|---|---|---|
| Validations module | inline + scattered helpers in `lend/utils/markets.ts` | fully inline in `SwapProvider.ts` | `borrow/core/validations.ts` (6 functions) |
| Protected method name | `validateMarketAllowed` (renamed for parity) | `validateMarketAllowed` | `validateMarketAllowed` ✅ |
| Method scope | bundles chain check + allowlist | allowlist + blocklist only | allowlist + blocklist only |
| Strict lookup-and-return helper | none (returns `undefined`) | `resolveMarketConfig` (returns `undefined`) | `requireAllowlistedMarketConfig` (throws) |
| Allowlist matcher helper | `lendMarketIdMatches` (extracted) | inline closure in `findMatchingConfig` (`SwapProvider.ts:493`) | `marketIdMatches` (extracted) |
| Tampered-config protection on write path | n/a | n/a | resolved in `resolveTrustedBaseParams` (write hooks receive allowlist-trusted config) |
| Walletaddress validation | inline `AddressRequiredError` | `validateNotZeroAddress` inline | `validateBorrowWalletAddress` (deduped 2-step check) |
| Quote validation helpers | n/a (no quotes) | inline (`validateQuoteExpiration`) | `validateQuoteAction` + `validateQuoteNotExpired` |
## Proposed consolidation
### 1. Per-domain `validations.ts` modules
- `lend/core/validations.ts`
- `swap/core/validations.ts`
Move existing scattered helpers in. Mirror borrow's pattern — pure functions taking config rather than `this`.
### 2. Lift to `BaseActionProvider`
`BaseActionProvider` is the right place for cross-domain shared protected methods. Candidates:
- `validateMarketAllowed(...)` — every domain has this with the same intent (allowlist + blocklist). The args differ (`marketId` vs `assetIn/assetOut/chainId` vs `BorrowMarketConfig`), so we'd need either a generic shape or domain-specific overrides. Likely a generic \`validateMarketAllowed(market, matcher)\` accepting a per-domain matcher.
- `requireAllowlistedMarketConfig(...)` — same pattern; strict lookup that throws.
- `validateWalletAddress(walletAddress)` — agnostic; move to `utils/validation.ts` if not already, drop the per-domain wrappers (borrow has \`validateBorrowWalletAddress\` — could become \`validateNonZeroWalletAddress\` shared).
### 3. Per-domain matcher helpers
- `lendMarketIdMatches` (already extracted)
- `marketIdMatches` (borrow, already extracted)
- **New:** `swapMarketPairMatches(assetIn, assetOut, chainId, pair)` — extract from inline closure in \`SwapProvider.ts:493\`.
### 4. Naming / scope alignment
- Lend's \`validateMarketAllowed\` currently bundles chain check + allowlist. Borrow and swap keep them separate. Decide a single convention and apply.
- Swap's \`resolveMarketConfig\` returns \`undefined\`. Borrow's \`requireAllowlistedMarketConfig\` throws. Add a consistent pair (\`findX\` returns, \`requireX\` throws) per domain.
### 5. Tampered-config protection
- Lend write paths (deposit/withdraw) currently trust caller-provided \`asset\` metadata. Audit whether a parallel \`resolveTrustedBaseParams\` is warranted.
- Swap is less exposed because the router doesn't take a marketParams hash — the asset addresses go directly into Permit2 / router calldata. Still worth a one-pass audit.
## Out of scope
- Behavior changes beyond what naming/structure consolidation surfaces.
- Cross-domain abstraction over market-id shape (lend uses `address`, swap uses asset pair, borrow uses `marketId` hash) — these are genuinely different and shouldn't be forced together.
## Acceptance
- All three domains have a \`/core/validations.ts\` module.
- Naming for shared concepts is identical across domains where the shape allows.
- Any logic identical across all three domains lives on \`BaseActionProvider\`.
- No behavioral regressions; existing tests still pass.
## Related history
The borrow consolidation that motivated this issue landed in branch \`kevin/borrow-pr3\` between commits \`0f1e980c\` (validations module) and \`d0b71326\` (lend rename for parity).
Contributor guide
Research direction
Compare BaseActionProvider with lend/core/validations.ts, swap/core/validations.ts, borrow/core/validations.ts, and the provider implementations, starting at SwapProvider.ts:493 and the borrow consolidation commits 0f1e980c and d0b71326. Run the existing test suite while aligning validation modules and shared naming; done means all three domains have validations.ts modules, shared identical logic is centralized, and tests pass without behavioral regressions.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- backend-api-design
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100