fix(recurring): bind batch schedule key to signed terms - #1754
Conversation
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
Greptile SummaryThis PR makes recurring-payment replay state independent of signature nonce and deadline, preventing re-signing from resetting paid indices.
Confidence Score: 5/5The PR appears safe to merge with no concrete changed-code failure identified. The executed recurring-payment path consistently derives one nonce-independent key for order checking, replay checking, and state updates, while reverted transfers leave payment state unmarked. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart LR
A[Signed schedule permit] --> B[Verify EIP-712 signature]
A --> C[Derive stable schedule key]
C --> D[Check order and paid bitmap]
D --> E[Execute token transfers]
E --> F[Mark index paid under schedule key]
G[Re-sign with new nonce or deadline] --> C
Reviews (1): Last reviewed commit: "fix(recurring): bind batch schedule key ..." | Re-trigger Greptile |

Decouple schedule key from EIP-712 digest to prevent replay via re-signing
The bitmap tracking which payment indices have been triggered was previously keyed on the EIP-712 digest, which includes
nonceanddeadline. This meant a subscriber could re-sign the same economic terms with a freshnonceordeadlineand reset the paid-index tracking, allowing already-settled payments to be triggered again.The schedule key is now derived exclusively from the fields that define the economic terms of the schedule (subscriber, token, recipient, amounts, period, etc.), deliberately excluding
nonceanddeadline. This ensures that re-signing with updated metadata does not produce a new key and cannot unlock previously paid indices.Changes
_scheduleKeyFromPermitand_scheduleKeyFromBatch(with public wrappers) to compute a stable, content-addressed key for each schedule.triggeredPaymentsBitmapandlastPaymentIndexare now keyed on the schedule key rather than the EIP-712 digest.ERC20RecurringPaymentProxy__ZeroScheduleIderror, reverted when a batch permit carries a zeroscheduleId, which would make distinct schedules collide._assertUnpaid,_assertOrder, and_markPaidhelpers to centralise bitmap and ordering logic._markPaidto after the transfer calls so a failed transfer never marks an index as paid.0before the existing upper-bound check.scheduleKeyFromPermit/scheduleKeyFromBatchand adds a dedicatedSchedule key replaysuite covering re-sign stability, zero-index rejection, key sensitivity to term changes, and zeroscheduleIdrejection.