Skip to content

fix(recurring): reject zero-amount legs - #1756

Closed
LeoSlrRf wants to merge 1 commit into
feat/req-429-fee-rescuefrom
feat/req-429-batch-trigger
Closed

fix(recurring): reject zero-amount legs#1756
LeoSlrRf wants to merge 1 commit into
feat/req-429-fee-rescuefrom
feat/req-429-batch-trigger

Conversation

@LeoSlrRf

@LeoSlrRf LeoSlrRf commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Add triggerRecurringPaymentBatch with multi-leg settlement support

Introduces a new triggerRecurringPaymentBatch entry point on ERC20RecurringPaymentProxy that allows a single scheduled payment cycle to fan out token transfers across multiple recipients ("legs") in one atomic transaction.

Key changes

Multi-leg settlement

  • A SchedulePermitBatch permit carries two leg arrays — initialLegs (used only on the first payment) and recurringLegs (used for all subsequent payments) — each capped at MAX_LEGS = 8.
  • _settleLegs iterates the active leg array and dispatches each transfer through the fee proxy with zero fee and a zero fee address.
  • _sumAndAssertLegs validates that no leg has a zero amount or a zero recipient address, and returns the total to pull from the subscriber.

Due-time enforcement

  • dueTimes must be a strictly-increasing array whose length equals totalPayments. The contract validates monotonicity on every call and reverts with ERC20RecurringPaymentProxy__InvalidDueTimes if the invariant is broken.

Atomicity guarantees

  • The bitmap is marked paid before token movement so that a revert in any leg rolls back the entire transaction, leaving balances and the bitmap unchanged.
  • Tests confirm that a blocked first, middle, or last leg each leave subscriber balances and the payment bitmap untouched.

Error surface

  • Removed ERC20RecurringPaymentProxy__IndexTooLarge (the uint8 parameter type already enforces the 0–255 range at the ABI level); the corresponding test was relocated and reframed.
  • Added ERC20RecurringPaymentProxy__InvalidDueTimes, ERC20RecurringPaymentProxy__TooManyLegs, ERC20RecurringPaymentProxy__EmptyLegs, and ERC20RecurringPaymentProxy__ZeroAmount.

_approveFeeProxy generalised

  • Now accepts an explicit IERC20FeeProxy argument so the batch path can pass the cached proxy reference without re-reading storage.

Test helpers

  • ERC20BlockRecipient test token added; it allows a specific recipient to be blocked so that mid-batch transfer failures can be simulated.
  • A worked example covering a four-payment schedule with two initial legs and four recurring legs is exercised end-to-end, asserting emitted events and final balances for both the initial and first recurring cycle.

@greptile-apps

greptile-apps Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds batch recurring-payment execution and rejects zero-amount legs during settlement, while preserving the existing single-payment fee-proxy path.

  • Adds validation for due times, leg counts, recipients, and amounts.
  • Pulls the aggregate batch amount before settling each selected leg atomically through the fee proxy.
  • Adds batch success, rollback, zero-address, ordering, and zero-amount tests.

Confidence Score: 4/5

The partial-execution path for schedules with malformed recurring legs should be fixed before merging.

A multi-payment permit can settle its valid initial payment before the contract discovers a zero-amount recurring leg, leaving the signed schedule permanently unable to complete.

Files Needing Attention: packages/smart-contracts/src/contracts/ERC20RecurringPaymentProxy.sol; packages/smart-contracts/test/contracts/ERC20RecurringPaymentProxy.test.ts

Important Files Changed

Filename Overview
packages/smart-contracts/src/contracts/ERC20RecurringPaymentProxy.sol Adds batch settlement and zero-amount validation, but validates only the currently selected leg array, allowing malformed schedules to execute partially.
packages/smart-contracts/src/contracts/test/ERC20PullTestTokens.sol Adds a test token that reverts transfers to a selected recipient for atomic rollback coverage.
packages/smart-contracts/test/contracts/ERC20RecurringPaymentProxy.test.ts Adds broad batch-path coverage, but the zero-amount test checks only the leg array selected by the first payment.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Relayer submits signed batch permit] --> B{Index 1 and initial legs non-empty?}
  B -->|Yes| C[Validate and settle initial legs]
  B -->|No| D[Validate and settle recurring legs]
  C --> E[Mark payment completed]
  D --> E
  C -. Recurring legs remain unchecked .-> F[Later recurring trigger]
  F --> D
Loading

Reviews (1): Last reviewed commit: "fix(recurring): reject zero-amount legs" | Re-trigger Greptile

Comment on lines +409 to +412
_assertUnpaid(scheduleKey, index);

bool useInitial = p.initialLegs.length != 0 && index == 1;
uint256 legsSum = useInitial

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Malformed schedules execute partially

When a multi-payment permit has valid initial legs but a zero-amount recurring leg, index 1 validates only initialLegs and completes successfully; index 2 then rejects recurringLegs, leaving the malformed schedule partially executed and unable to complete.

@LeoSlrRf LeoSlrRf closed this Aug 24, 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