Feat: Generic ERC4626 Shield validator + comprehensive test suite#8
Feat: Generic ERC4626 Shield validator + comprehensive test suite#8
Conversation
📝 WalkthroughWalkthroughAdds a new ERC4626Validator module (implementation, types, exports) and a comprehensive test suite. The validator decodes EVM transactions, enforces per-chain vault whitelists, and validates APPROVAL, WRAP, SUPPLY, WITHDRAW, and UNWRAP flows with vault/amount/recipient/ETH checks. Changes
Sequence DiagramsequenceDiagram
participant Caller
participant ERC4626Validator
participant TxDecoder
participant VaultConfig
participant TypeHandler
Caller->>ERC4626Validator: validate(unsignedTx, txType?, user, args?)
ERC4626Validator->>TxDecoder: decode unsignedTx -> to, data, value, chainId
TxDecoder-->>ERC4626Validator: decoded tx fields
ERC4626Validator->>VaultConfig: resolve vault by chainId/to & get WETH
VaultConfig-->>ERC4626Validator: vaultInfo / wethAddress
ERC4626Validator->>TypeHandler: dispatch to handler based on txType or auto-detect
TypeHandler->>VaultConfig: check whitelist, canEnter/canExit, isWethVault
TypeHandler-->>ERC4626Validator: validation result (allowed/blocked + reason)
ERC4626Validator-->>Caller: return ValidationResult
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 6✅ Passed checks (6 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
No actionable comments were generated in the recent review. 🎉 🧹 Recent nitpick comments
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/validators/evm/erc4626/erc4626.validator.test.ts`:
- Around line 1-18: Prettier is flagging formatting issues (e.g., trailing
whitespace) in the ERC4626 unit test file; run a formatting pass (npx prettier
--write) on the file and remove any trailing spaces and other style violations
so the file containing constants like USER_ADDRESS, VAULT_ADDRESS, INPUT_TOKEN,
WETH_ARBITRUM, etc. is properly formatted; ensure the updated file is saved and
committed so CI Prettier checks pass.
In `@src/validators/evm/erc4626/erc4626.validator.ts`:
- Around line 99-100: The function parameter args is unused and should be
renamed to _args to match the existing unused parameter convention (_context)
and satisfy the linter; update the function signature that currently declares
"args?: ActionArguments" to " _args?: ActionArguments" (keep the type
ValidationContext and the _context parameter as-is) so the unused parameter is
clearly prefixed with an underscore.
🧹 Nitpick comments (1)
src/validators/evm/erc4626/erc4626.validator.ts (1)
116-124: Consider more explicit null/undefined checks for better clarity.Static analysis flags that
!chainIdmay conflate0,null, andundefined. While chain ID0is not valid for EVM chains in practice, being explicit improves readability and silences the warning.♻️ Optional: More explicit checks
// Get and validate chain ID from transaction const chainId = this.getNumericChainId(tx); - if (!chainId) { + if (chainId === null || chainId === undefined) { return this.blocked('Chain ID not found in transaction'); } // Ensure destination address exists - if (!tx.to) { + if (tx.to === null || tx.to === undefined) { return this.blocked('Transaction has no destination address'); }
Summary by CodeRabbit
New Features
Tests
Introduces a new generic ERC-4626 transaction validator for Shield. This validator can validate unsigned transactions for any ERC4626 vault across all supported EVM chains, covering the full deposit/withdraw lifecycle. It is not yet registered in Shield's validator registry (that's PR2) — this PR focuses on getting the validator logic and test coverage reviewed and merged.
Why
Shield currently validates Lido, Solana, and Tron transactions. We have ~1,400 ERC4626 vault yield IDs (Euler, Morpho, Yearn V3, Fluid, Aave, etc.) with no Shield coverage. This validator provides a single, reusable adapter that works for all of them since ERC-4626 is a standardized interface.
How it works
Architecture
The
ERC4626ValidatorextendsBaseEVMValidatorand validates 5 transaction types that represent the full ERC4626 lifecycle:approve(spender, amount)allowing the vault to pull tokens from the userdeposit()converting native ETH to WETH (only for vaults that accept WETH as input)deposit(assets, receiver)ormint(shares, receiver)into a whitelisted vaultwithdraw(assets, receiver, owner)orredeem(shares, receiver, owner)from a whitelisted vaultwithdraw(amount)converting WETH back to native ETH after withdrawalValidation flow
Every path also runs calldata tamper detection: the raw calldata is parsed via the ABI, then re-encoded from the parsed args and compared byte-for-byte against the original. Any appended, modified, or truncated bytes are caught.
Configuration
The validator is instantiated with a
VaultConfigurationcontaining an array ofVaultInfoobjects:Vault lookups use a composite
chainId:addresskey, so the same contract address on two different chains is handled correctly.WETH addresses
A hardcoded map of canonical WETH contract addresses per chain is used for WRAP/UNWRAP validation. Currently covers: Ethereum, Arbitrum, Optimism, Base, Polygon, Gnosis, Avalanche, Binance, Sonic, Unichain.
Integration with Shield
This validator is not yet registered in the validator registry. In production, Shield's
validate()method doesn't receive atransactionType— it loops through all 5 types and expects exactly one to return valid. The auto-detection tests in this PR simulate that behavior to prove there's no ambiguity between types.Registration happens in PR2, where each
yieldIdfrom an embeddedvault-registry.jsonwill be mapped to a sharedERC4626Validatorinstance.Files
Test coverage
49 tests across 7 describe blocks:
tx.to!=inputTokenAddress), max uint256tx.toswap (core attack vector), wrong chain, wrongfrom, unknown selector, WETH vault with ETH value, zero amountfrom, missingchainId, nulltx.toHow to test
cd shield/parent pnpm install npx jest --testPathPattern=erc4626What this PR does NOT do (deferred)
vault-registry.jsonand register the validator invalidators/index.tsso Shield routes realyieldIds to itSecurity checklist
chainId:addresscomposite key)tx.tovalidated against whitelist for SUPPLY/WITHDRAWtx.tovalidated againstinputTokenAddressfor APPROVALfromaddress validated as user in shared path (all 5 types)canEnter/canExitflags