feat(erc4626): accept zero-amount transactions instead of blocking (ENG-2372) - #28
feat(erc4626): accept zero-amount transactions instead of blocking (ENG-2372)#28ajag408 wants to merge 4 commits into
Conversation
📝 WalkthroughWalkthrough
ChangesZERO_AMOUNT Non-Blocking Flag
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/validators/evm/erc4626/erc4626.validator.ts (1)
234-254: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winStale comment contradicts new behavior.
The comment
// WRAP must send ETH valueno longer holds—zero-value wraps are now accepted and flagged asZERO_AMOUNT. Update it to avoid misleading future readers of this security validator.📝 Suggested comment update
- // WRAP must send ETH value + // WRAP ETH value (zero is accepted and flagged as ZERO_AMOUNT) const value = BigInt(tx.value ?? '0');🤖 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/erc4626/erc4626.validator.ts` around lines 234 - 254, The inline comment above the wrap validation in erc4626.validator.ts is stale and contradicts the current ZERO_AMOUNT handling. Update the comment near the `value` check and `parseAndValidateCalldata` logic to reflect that zero-value wraps are accepted and reported as `ZERO_AMOUNT`, so future readers are not misled about the `deposit` flow in `ERC4626Validator`.
🤖 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.
Outside diff comments:
In `@src/validators/evm/erc4626/erc4626.validator.ts`:
- Around line 234-254: The inline comment above the wrap validation in
erc4626.validator.ts is stale and contradicts the current ZERO_AMOUNT handling.
Update the comment near the `value` check and `parseAndValidateCalldata` logic
to reflect that zero-value wraps are accepted and reported as `ZERO_AMOUNT`, so
future readers are not misled about the `deposit` flow in `ERC4626Validator`.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 0109fc18-f89b-4f20-817c-e58026189c7f
📒 Files selected for processing (5)
package.jsonsrc/types/index.tssrc/validators/base.validator.tssrc/validators/evm/erc4626/erc4626.validator.test.tssrc/validators/evm/erc4626/erc4626.validator.ts
There was a problem hiding this comment.
Risk: medium. Left a non-blocking comment and did not approve: this PR changes Shield ERC-4626 validation from blocking to accepting zero-amount wrap/supply/withdraw/unwrap, which is above the low-risk approval threshold. Human review is needed; no additional reviewers assigned because reviewers are already requested. Bugbot and Security Agent completed successfully with no findings requiring attention.
Sent by Cursor Approval Agent: Pull Request Router and Approver


Summary
Shield now accepts zero-amount ERC-4626 transactions for recognized operation
patterns instead of rejecting them. Previously, a zero-amount
deposit/mint/withdraw/redeem/unwrap/ wrap (tx.value === 0) was hard-blocked(
"… amount is zero"/"WRAP transaction must send ETH value"), which surfacedto clients as the aggregate
"No matching operation pattern found"error — theroot cause of the
redeem(0)false blocks seen in production soak (ENG-2372).Why this approach: we don't know why clients construct zero-amount txs and
don't want to break unknown flows. A zero-amount transaction is a no-op with no
visible security risk, so blocking it adds friction without protection. This
supersedes the earlier plan to reject
amount <= 0upstream in the monorepo witha 412.
Scope is amount-only. Acceptance does not loosen any other invariant. A
zero-amount tx still must pass every existing check — whitelisted vault, correct
selector, no stray ETH value, and
owner/receivermust equal theuser / expected address. Declared-amount intent matching from
mainis unchanged:if
args.amount/args.shareAmountis present and does not match calldata, thetx is still blocked (including wrap
value === 0vs a non-zero declared amount).Only the
amount === 0/value === 0short-circuit reject is removed.UNWRAP still has no numeric declared-amount check — that is
main's design(WETH-exit follow-up;
validateUnwrap(tx, chainId)only). This PR does notchange that.
Telemetry. When an accepted transaction has a zero amount, the result now
carries a non-blocking
details.flags: ['ZERO_AMOUNT']signal so we can observehow often and why clients send these, and revisit a targeted block later if the
data warrants it. Consuming this flag in monorepo
ShieldValidationServicelogsis a follow-up (needs the
@yieldxyz/shieldbump).Changes
src/types/index.ts— add optionaldetails.flags?: string[]toValidationResult.src/validators/base.validator.ts—safe(flags?)can attach non-blocking flags.src/validators/evm/erc4626/erc4626.validator.ts— remove the zero-amount blocksin
validateSupply,validateWithdraw,validateUnwrap, andvalidateWrap;emit
ZERO_AMOUNTwhen the amount / wrap value is zero. Receiver, owner, anddeclared-amount intent checks from
mainstill run.src/validators/evm/erc4626/erc4626.validator.test.ts— flip zero-amountreject-tests (2-arg deposit, Sky 3-arg deposit, withdraw, unwrap, wrap) to
assert acceptance + flag; add guard tests (non-whitelisted vault, receiver
mismatch, owner mismatch); add wrap
value === 0vs declaredamountcases.package.json—1.5.0→1.6.0(backward-compatible behavior change on topof current
main).approve(0)is unchanged — the approval validator already allows it (USDTzero-allowance reset) and skips intent matching for zero approvals. That is the
precedent this change follows.
QA Proof
Full Shield test suite green after merging

main, with flipped zero-amountacceptance cases, amount-only guard tests, and wrap declared-amount interaction
tests:
What Needs to Be QA'd in Staging
SHIELD_MODE=MONITOR; send a zero-amountredeem(0)/withdraw(0)on a whitelisted ERC-4626 yield and confirm logs now showShield validation successful(wasShield validation failed/No matching operation pattern).ZERO_AMOUNTflag is present on the accepted result (oncethis package is bumped in the monorepo; until then the tx should at least
stop failing Shield).
value = 0is accepted (same flag),unless
args.amountis a non-zero declared intent — that must still block.owner/receiver(not theuser) is still blocked.
before (
flagsabsent on the result).args.amountis still blocked).QA Team Notification
Note
Medium Risk
Changes transaction-validation outcomes for zero-amount ERC-4626 flows that integrators may rely on for blocking; security invariants besides amount are unchanged but behavior is user-visible in Shield.
Overview
ERC-4626 validation no longer hard-fails on zero amounts for wrap, supply, withdraw, and unwrap when all other checks pass (whitelisted vault, correct selectors, receiver/owner, declared intent, etc.). Accepted zero-amount txs return
isValid: truewith a non-blockingdetails.flagsentryZERO_AMOUNTfor observability.API surface:
ValidationResult.details.flagsis added, andBaseValidator.safe()can attach optional flags. Package version is bumped to 1.6.0.Tests are updated from reject-to-accept for zero amounts and add regressions proving malicious zero-amount txs (wrong vault, receiver, owner) still block.
Reviewed by Cursor Bugbot for commit 34fd645. Configure here.