From df9a8464e5c11d769d99d0f6305193eccb8d9c7d Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Fri, 17 Apr 2026 07:52:46 +0000 Subject: [PATCH 01/31] docs: update audit extracts for PR1301 v02 report Update existing findings with status, team responses, and mitigation reviews from the v02 audit report. Add 17 new findings (M-4, M-5, L-6 through L-11, R-5 through R-13) and the v02 PDF. --- .../audits/PR1301/Graph_PR1301_v02.pdf | Bin 0 -> 589789 bytes packages/issuance/audits/PR1301/README.md | 62 +++++++++++------- packages/issuance/audits/PR1301/TRST-H-1.md | 8 ++- packages/issuance/audits/PR1301/TRST-H-2.md | 8 ++- packages/issuance/audits/PR1301/TRST-H-3.md | 8 ++- packages/issuance/audits/PR1301/TRST-H-4.md | 8 ++- packages/issuance/audits/PR1301/TRST-L-1.md | 8 ++- packages/issuance/audits/PR1301/TRST-L-10.md | 22 +++++++ packages/issuance/audits/PR1301/TRST-L-11.md | 26 ++++++++ packages/issuance/audits/PR1301/TRST-L-2.md | 8 ++- packages/issuance/audits/PR1301/TRST-L-3.md | 8 ++- packages/issuance/audits/PR1301/TRST-L-4.md | 4 +- packages/issuance/audits/PR1301/TRST-L-5.md | 8 ++- packages/issuance/audits/PR1301/TRST-L-6.md | 24 +++++++ packages/issuance/audits/PR1301/TRST-L-7.md | 22 +++++++ packages/issuance/audits/PR1301/TRST-L-8.md | 22 +++++++ packages/issuance/audits/PR1301/TRST-L-9.md | 22 +++++++ packages/issuance/audits/PR1301/TRST-M-1.md | 6 +- packages/issuance/audits/PR1301/TRST-M-2.md | 8 ++- packages/issuance/audits/PR1301/TRST-M-3.md | 4 +- packages/issuance/audits/PR1301/TRST-M-4.md | 24 +++++++ packages/issuance/audits/PR1301/TRST-M-5.md | 24 +++++++ packages/issuance/audits/PR1301/TRST-R-10.md | 7 ++ packages/issuance/audits/PR1301/TRST-R-11.md | 7 ++ packages/issuance/audits/PR1301/TRST-R-12.md | 7 ++ packages/issuance/audits/PR1301/TRST-R-13.md | 7 ++ packages/issuance/audits/PR1301/TRST-R-5.md | 7 ++ packages/issuance/audits/PR1301/TRST-R-6.md | 7 ++ packages/issuance/audits/PR1301/TRST-R-7.md | 7 ++ packages/issuance/audits/PR1301/TRST-R-8.md | 7 ++ packages/issuance/audits/PR1301/TRST-R-9.md | 7 ++ .../indexing-agreement/cancel.t.sol | 4 +- 32 files changed, 353 insertions(+), 48 deletions(-) create mode 100644 packages/issuance/audits/PR1301/Graph_PR1301_v02.pdf create mode 100644 packages/issuance/audits/PR1301/TRST-L-10.md create mode 100644 packages/issuance/audits/PR1301/TRST-L-11.md create mode 100644 packages/issuance/audits/PR1301/TRST-L-6.md create mode 100644 packages/issuance/audits/PR1301/TRST-L-7.md create mode 100644 packages/issuance/audits/PR1301/TRST-L-8.md create mode 100644 packages/issuance/audits/PR1301/TRST-L-9.md create mode 100644 packages/issuance/audits/PR1301/TRST-M-4.md create mode 100644 packages/issuance/audits/PR1301/TRST-M-5.md create mode 100644 packages/issuance/audits/PR1301/TRST-R-10.md create mode 100644 packages/issuance/audits/PR1301/TRST-R-11.md create mode 100644 packages/issuance/audits/PR1301/TRST-R-12.md create mode 100644 packages/issuance/audits/PR1301/TRST-R-13.md create mode 100644 packages/issuance/audits/PR1301/TRST-R-5.md create mode 100644 packages/issuance/audits/PR1301/TRST-R-6.md create mode 100644 packages/issuance/audits/PR1301/TRST-R-7.md create mode 100644 packages/issuance/audits/PR1301/TRST-R-8.md create mode 100644 packages/issuance/audits/PR1301/TRST-R-9.md diff --git a/packages/issuance/audits/PR1301/Graph_PR1301_v02.pdf b/packages/issuance/audits/PR1301/Graph_PR1301_v02.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e9512ec7ee7fb2da516b299461a93a06fed9299d GIT binary patch literal 589789 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 #YL?^@m<^Ijg66`N>_?L8b7+GLEa1em@ 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%0DB$A3b_7AZWd9Ac{$;u+M(8dbviy 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 z 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 literal 0 HcmV?d00001 diff --git a/packages/issuance/audits/PR1301/README.md b/packages/issuance/audits/PR1301/README.md index 46695b14a..c8c0000c1 100644 --- a/packages/issuance/audits/PR1301/README.md +++ b/packages/issuance/audits/PR1301/README.md @@ -1,35 +1,53 @@ -# Trust Security Audit - PR #1301 +# Trust Security Audit - PR #1301 / #1312 **Auditor:** Trust Security **Period:** 2026-03-03 to 2026-03-19 **Commit:** 7405c9d5f73bce04734efb3f609b76d95ffb520e -**Report:** [Graph_PR1301_v01.pdf](Graph_PR1301_v01.pdf) +**Fix review commit:** 0bbb476f37f85d042927e84d8764fa58eb020ccf +**Report:** [Graph_PR1301_v02.pdf](Graph_PR1301_v02.pdf) ## Findings Summary -| ID | Title | Severity | -| ----------------------- | -------------------------------------------------------- | -------- | -| [TRST-H-1](TRST-H-1.md) | Malicious payer gas siphoning via 63/64 rule | High | -| [TRST-H-2](TRST-H-2.md) | Invalid supportsInterface() returndata escapes try/catch | High | -| [TRST-H-3](TRST-H-3.md) | Stale escrow snapshot causes perpetual revert loop | High | -| [TRST-H-4](TRST-H-4.md) | EOA payer can block collection via EIP-7702 | High | -| [TRST-M-1](TRST-M-1.md) | Micro-thaw griefing via permissionless depositTo() | Medium | -| [TRST-M-2](TRST-M-2.md) | tempJit fallback in beforeCollection() unreachable | Medium | -| [TRST-M-3](TRST-M-3.md) | Instant escrow mode degradation via agreement offer | Medium | -| [TRST-L-1](TRST-L-1.md) | Insufficient gas for afterCollection callback | Low | -| [TRST-L-2](TRST-L-2.md) | Pending update over-reserves escrow | Low | -| [TRST-L-3](TRST-L-3.md) | Unsafe approveAgreement behavior during pause | Low | -| [TRST-L-4](TRST-L-4.md) | Pair tracking removal blocked by 1 wei donation | Low | -| [TRST-L-5](TRST-L-5.md) | \_computeMaxFirstClaim overestimates near deadline | Low | +| ID | Title | Severity | Status | +| ------------------------- | -------------------------------------------------------- | -------- | ------------ | +| [TRST-H-1](TRST-H-1.md) | Malicious payer gas siphoning via 63/64 rule | High | Fixed | +| [TRST-H-2](TRST-H-2.md) | Invalid supportsInterface() returndata escapes try/catch | High | Fixed | +| [TRST-H-3](TRST-H-3.md) | Stale escrow snapshot causes perpetual revert loop | High | Fixed | +| [TRST-H-4](TRST-H-4.md) | EOA payer can block collection via EIP-7702 | High | Fixed | +| [TRST-M-1](TRST-M-1.md) | Micro-thaw griefing via permissionless depositTo() | Medium | Open | +| [TRST-M-2](TRST-M-2.md) | tempJit fallback in beforeCollection() unreachable | Medium | Fixed | +| [TRST-M-3](TRST-M-3.md) | Instant escrow mode degradation via agreement offer | Medium | Acknowledged | +| [TRST-M-4](TRST-M-4.md) | Returndata bombing via payer callbacks | Medium | Open | +| [TRST-M-5](TRST-M-5.md) | Perpetual thaw griefing via micro deposits | Medium | Open | +| [TRST-L-1](TRST-L-1.md) | Insufficient gas for afterCollection callback | Low | Fixed | +| [TRST-L-2](TRST-L-2.md) | Pending update over-reserves escrow | Low | Fixed | +| [TRST-L-3](TRST-L-3.md) | Unsafe approveAgreement behavior during pause | Low | Fixed | +| [TRST-L-4](TRST-L-4.md) | Pair tracking removal blocked by 1 wei donation | Low | Acknowledged | +| [TRST-L-5](TRST-L-5.md) | \_computeMaxFirstClaim overestimates near deadline | Low | Fixed | +| [TRST-L-6](TRST-L-6.md) | Update offer cleanup bypassed via planted offer | Low | Open | +| [TRST-L-7](TRST-L-7.md) | cancel() order sensitivity leaves RCAU offer unreachable | Low | Open | +| [TRST-L-8](TRST-L-8.md) | EOA payer signatures cannot be revoked before deadline | Low | Open | +| [TRST-L-9](TRST-L-9.md) | Callback gas precheck does not account for overhead | Low | Open | +| [TRST-L-10](TRST-L-10.md) | EIP-7702 payer code change enables callback gas griefing | Low | Open | +| [TRST-L-11](TRST-L-11.md) | Inaccurate state flags in getAgreementDetails() | Low | Open | ## Recommendations -| ID | Title | -| ----------------------- | ---------------------------------------------- | -| [TRST-R-1](TRST-R-1.md) | Avoid redeployment of RewardsEligibilityOracle | -| [TRST-R-2](TRST-R-2.md) | Improve stale documentation | -| [TRST-R-3](TRST-R-3.md) | Incorporate defensive coding best practices | -| [TRST-R-4](TRST-R-4.md) | Document critical assumptions in the RAM | +| ID | Title | +| ------------------------- | --------------------------------------------------------------- | +| [TRST-R-1](TRST-R-1.md) | Avoid redeployment of RewardsEligibilityOracle | +| [TRST-R-2](TRST-R-2.md) | Improve stale documentation | +| [TRST-R-3](TRST-R-3.md) | Incorporate defensive coding best practices | +| [TRST-R-4](TRST-R-4.md) | Document critical assumptions in the RAM | +| [TRST-R-5](TRST-R-5.md) | Ambiguous return value in getAgreementOfferAt() | +| [TRST-R-6](TRST-R-6.md) | Dead code guard in \_validateAndStoreUpdate() | +| [TRST-R-7](TRST-R-7.md) | Remove consumed offers in accept() and update() | +| [TRST-R-8](TRST-R-8.md) | Align pause documentation with callback behavior in the RAM | +| [TRST-R-9](TRST-R-9.md) | \_isAuthorized() override trusts itself for any authorizer | +| [TRST-R-10](TRST-R-10.md) | Document role-change semantics for existing agreements | +| [TRST-R-11](TRST-R-11.md) | Remove or implement unused state flags in IAgreementCollector | +| [TRST-R-12](TRST-R-12.md) | Document ACCEPTED state returned for cancelled agreements | +| [TRST-R-13](TRST-R-13.md) | Document reclaim reason change for stale allocation force-close | ## Centralization Risks diff --git a/packages/issuance/audits/PR1301/TRST-H-1.md b/packages/issuance/audits/PR1301/TRST-H-1.md index f250ee55c..7c15cd250 100644 --- a/packages/issuance/audits/PR1301/TRST-H-1.md +++ b/packages/issuance/audits/PR1301/TRST-H-1.md @@ -3,7 +3,7 @@ - **Severity:** High - **Category:** Gas-related issues - **Source:** RecurringCollector.sol -- **Status:** Open +- **Status:** Fixed ## Description @@ -19,7 +19,11 @@ Enforce a minimum gas reservation before each callback. Before calling `beforeCo ## Team Response -TBD +Fixed. + +## Mitigation Review + +Issue has been fixed as suggested. --- diff --git a/packages/issuance/audits/PR1301/TRST-H-2.md b/packages/issuance/audits/PR1301/TRST-H-2.md index 0f2acbffa..3f8eea841 100644 --- a/packages/issuance/audits/PR1301/TRST-H-2.md +++ b/packages/issuance/audits/PR1301/TRST-H-2.md @@ -3,7 +3,7 @@ - **Severity:** High - **Category:** Logical flaws - **Source:** RecurringCollector.sol -- **Status:** Open +- **Status:** Fixed ## Description @@ -19,7 +19,11 @@ Avoid receiving and decoding values from untrusted contract calls. This can be d ## Team Response -TBD +Fixed. + +## Mitigation Review + +Fixed. The affected code has been refactored, addressing the issue. --- diff --git a/packages/issuance/audits/PR1301/TRST-H-3.md b/packages/issuance/audits/PR1301/TRST-H-3.md index 5fac18493..66bddea4d 100644 --- a/packages/issuance/audits/PR1301/TRST-H-3.md +++ b/packages/issuance/audits/PR1301/TRST-H-3.md @@ -3,7 +3,7 @@ - **Severity:** High - **Category:** Logical flaws - **Source:** RecurringAgreementManager.sol -- **Status:** Open +- **Status:** Fixed ## Description @@ -21,7 +21,11 @@ Read the fresh escrow balance inside `_escrowMinMax()` when computing the defici ## Team Response -TBD +Fixed. + +## Mitigation Review + +The new code has a `_setEscrowSnap()` call before `_escrowMinMax()`, ensuring the snapshot is updated and fixing the root cause. --- diff --git a/packages/issuance/audits/PR1301/TRST-H-4.md b/packages/issuance/audits/PR1301/TRST-H-4.md index 80b4c4195..dda0b4f17 100644 --- a/packages/issuance/audits/PR1301/TRST-H-4.md +++ b/packages/issuance/audits/PR1301/TRST-H-4.md @@ -3,7 +3,7 @@ - **Severity:** High - **Category:** Type confusion - **Source:** RecurringCollector.sol -- **Status:** Open +- **Status:** Fixed ## Description @@ -21,7 +21,11 @@ Record whether the payer had code at agreement acceptance time by adding a bool ## Team Response -TBD +Fixed. + +## Mitigation Review + +Fixed under the assumption that a provider setting `CONDITION_ELIGIBILITY_CHECK` to true must trust the payer contract. The statement in the fix comment that "An EOA cannot pass this check, so an EOA cannot create an agreement with eligibility gating enabled" is inaccurate, because an EOA can always change its code back and forth via EIP-7702 to pass interface checks. The correct security boundary is that the provider trusts the payer contract when opting into eligibility, not that the payer cannot be an EOA. --- diff --git a/packages/issuance/audits/PR1301/TRST-L-1.md b/packages/issuance/audits/PR1301/TRST-L-1.md index 512e00e98..ed4cd9f11 100644 --- a/packages/issuance/audits/PR1301/TRST-L-1.md +++ b/packages/issuance/audits/PR1301/TRST-L-1.md @@ -3,7 +3,7 @@ - **Severity:** Low - **Category:** Time sensitivity flaw - **Source:** RecurringCollector.sol -- **Status:** Open +- **Status:** Fixed ## Description @@ -19,7 +19,11 @@ Enforce a minimum gas forwarding requirement for the `afterCollection()` callbac ## Team Response -TBD +Fixed. + +## Mitigation Review + +Fixed as suggested. --- diff --git a/packages/issuance/audits/PR1301/TRST-L-10.md b/packages/issuance/audits/PR1301/TRST-L-10.md new file mode 100644 index 000000000..385b4ee0c --- /dev/null +++ b/packages/issuance/audits/PR1301/TRST-L-10.md @@ -0,0 +1,22 @@ +# TRST-L-10: EIP-7702 payer code change enables callback gas griefing after acceptance + +- **Severity:** Low +- **Category:** Type confusion +- **Source:** RecurringCollector.sol +- **Status:** Open + +## Description + +Under EIP-7702, which is live on Ethereum mainnet and Arbitrum, an EOA can install arbitrary code via a delegation transaction. `_preCollectCallbacks()` and `_postCollectCallback()` dispatch the `beforeCollection()` and `afterCollection()` callbacks only when `payer.code.length != 0`. A payer who accepted an agreement as an EOA can later acquire code, and have the callbacks dispatched against delegated code that the service provider never considered at acceptance time. + +The callbacks are low level calls with a `MAX_PAYER_CALLBACK_GAS` budget, and they are vulnerable to the returndata bombing vector described in TRST-M-4, on top of the baseline call costs. Service providers estimate gas for `collect()` under the assumption that the payer is an EOA with no callbacks. If the payer is a contract at collection time, the provider's gas estimate may be insufficient and the transaction will revert with griefed gas. This is a distinct attack surface from TRST-H-4, which targeted the eligibility gate rather than the callback path. + +## Recommended Mitigation + +Use the introduced `CONDITION_ELIGIBILITY_CHECK` flag in place of the live `code.length` check in `_preCollectCallbacks()` and `_postCollectCallback()`. This freezes the contract-versus-EOA determination to the state the service provider observed at acceptance. + +## Team Response + +TBD + +--- diff --git a/packages/issuance/audits/PR1301/TRST-L-11.md b/packages/issuance/audits/PR1301/TRST-L-11.md new file mode 100644 index 000000000..ad0771c7e --- /dev/null +++ b/packages/issuance/audits/PR1301/TRST-L-11.md @@ -0,0 +1,26 @@ +# TRST-L-11: Inaccurate state flags returned by getAgreementDetails() and \_offerUpdate() + +- **Severity:** Low +- **Category:** Logical flaws +- **Source:** RecurringCollector.sol +- **Status:** Open + +## Description + +The `IAgreementCollector` interface defines state bit flags including `ACCEPTED` and `UPDATE`, with the documented convention that `UPDATE` is ORed into the state returned by `getAgreementDetails()` for pending versions (index 1). Two deviations from the specification were observed. + +First, in `_offerUpdate()` (lines 417 to 455), when an update is offered against an already accepted agreement, the returned `AgreementDetails` sets state to `REGISTERED | UPDATE` without ORing `ACCEPTED`. Callers that inspect the returned state to determine whether the agreement is already live will misread the underlying agreement as not accepted. + +Second, in `getAgreementDetails()` (lines 500 to 528), the `UPDATE` bit is never ORed into the returned state for the pending version path. The interface documentation promises this behavior for pending versions, but the implementation returns `REGISTERED` or `ACCEPTED` without regard to whether an RCAU offer is pending. + +Neither deviation changes on-chain accounting, but integrators relying on the declared state semantics will receive misleading data. + +## Recommended Mitigation + +In `_offerUpdate()`, OR the `ACCEPTED` bit into state when the underlying agreement is in the Accepted state. In `getAgreementDetails()`, OR the `UPDATE` bit into the returned state when a pending RCAU offer exists for the agreement. + +## Team Response + +TBD + +--- diff --git a/packages/issuance/audits/PR1301/TRST-L-2.md b/packages/issuance/audits/PR1301/TRST-L-2.md index 3fd0d45e4..f3eee05c5 100644 --- a/packages/issuance/audits/PR1301/TRST-L-2.md +++ b/packages/issuance/audits/PR1301/TRST-L-2.md @@ -3,7 +3,7 @@ - **Severity:** Low - **Category:** Arithmetic issues - **Source:** RecurringAgreementManager.sol -- **Status:** Open +- **Status:** Fixed ## Description @@ -19,7 +19,11 @@ The `pendingMaxNextClaim` should be computed as stated above, then reduced by th ## Team Response -TBD +Fixed. + +## Mitigation Review + +Refactored so that at any point, the accurate worst-case collection is reflected. --- diff --git a/packages/issuance/audits/PR1301/TRST-L-3.md b/packages/issuance/audits/PR1301/TRST-L-3.md index ff8edd1a8..92a21e7e4 100644 --- a/packages/issuance/audits/PR1301/TRST-L-3.md +++ b/packages/issuance/audits/PR1301/TRST-L-3.md @@ -3,7 +3,7 @@ - **Severity:** Low - **Category:** Access control issues - **Source:** RecurringAgreementManager.sol -- **Status:** Open +- **Status:** Fixed ## Description @@ -19,7 +19,11 @@ Add a pause check to `approveAgreement()` that returns `bytes4(0)` when the cont ## Team Response -TBD +Fixed. + +## Mitigation Review + +Fixed. Underlying code has been refactored, addressing the issue. --- diff --git a/packages/issuance/audits/PR1301/TRST-L-4.md b/packages/issuance/audits/PR1301/TRST-L-4.md index 71ea33109..4df8bbef9 100644 --- a/packages/issuance/audits/PR1301/TRST-L-4.md +++ b/packages/issuance/audits/PR1301/TRST-L-4.md @@ -3,7 +3,7 @@ - **Severity:** Low - **Category:** Donation attacks - **Source:** RecurringAgreementManager.sol -- **Status:** Open +- **Status:** Acknowledged ## Description @@ -19,7 +19,7 @@ In `_reconcilePairTracking()`, base the removal decision on `pairAgreementCount` ## Team Response -TBD +Accepted limitation. Orphaned tracking entries do not affect correctness or funds safety. --- diff --git a/packages/issuance/audits/PR1301/TRST-L-5.md b/packages/issuance/audits/PR1301/TRST-L-5.md index 812ac5c35..2533503e0 100644 --- a/packages/issuance/audits/PR1301/TRST-L-5.md +++ b/packages/issuance/audits/PR1301/TRST-L-5.md @@ -3,7 +3,7 @@ - **Severity:** Low - **Category:** Logical flaw - **Source:** RecurringAgreementManager.sol -- **Status:** Open +- **Status:** Fixed ## Description @@ -19,7 +19,11 @@ Align `_computeMaxFirstClaim()` with the RecurringCollector's `getMaxNextClaim() ## Team Response -TBD +Fixed. + +## Mitigation Review + +Fixed. The RecurringCollector now calculates the effective window correctly. --- diff --git a/packages/issuance/audits/PR1301/TRST-L-6.md b/packages/issuance/audits/PR1301/TRST-L-6.md new file mode 100644 index 000000000..c0792c908 --- /dev/null +++ b/packages/issuance/audits/PR1301/TRST-L-6.md @@ -0,0 +1,24 @@ +# TRST-L-6: Update offer cleanup bypassed via planted offer matching active terms + +- **Severity:** Low +- **Category:** Logical flaws +- **Source:** RecurringCollector.sol +- **Status:** Open + +## Description + +In `_validateAndStoreUpdate()` (lines 854-858), cleanup of stored offers after an update uses an if / else if chain keyed on the prior `activeTermsHash`. The first branch deletes a matching entry from `rcaOffers`; the second deletes a matching entry from `rcauOffers`. + +A payer who observes a pending update can call `offer()` with `OFFER_TYPE_NEW` and parameters that reproduce the agreement's currently active RCA terms. The resulting entry in `rcaOffers` hashes to the same `oldHash` value. When `update()` later reaches the cleanup block, the first branch matches and deletes the planted entry, and the else if branch that would have cleaned up the corresponding `rcauOffers` entry is skipped. The pending update offer is then orphaned in storage. + +The `updateNonce` check elsewhere in `_validateAndStoreUpdate()` prevents the orphaned RCAU from being re-accepted, so the issue does not translate to a direct economic exploit. However, it introduces a divergence between the documented invariant that replaced offers are cleaned up and the actual storage state, which could surface as a correctness issue in future features that rely on offer presence. + +## Recommended Mitigation + +Delete both `rcaOffers[agreementId]` and `rcauOffers[agreementId]` unconditionally at the end of `_validateAndStoreUpdate()`. After a successful update the agreement's active terms have changed and any pre-existing offer entries for the same `agreementId` are stale by definition. + +## Team Response + +TBD + +--- diff --git a/packages/issuance/audits/PR1301/TRST-L-7.md b/packages/issuance/audits/PR1301/TRST-L-7.md new file mode 100644 index 000000000..1eee39005 --- /dev/null +++ b/packages/issuance/audits/PR1301/TRST-L-7.md @@ -0,0 +1,22 @@ +# TRST-L-7: The cancel() function order sensitivity leaves RCAU offer unreachable + +- **Severity:** Low +- **Category:** Time-sensitivity issues +- **Source:** RecurringCollector.sol +- **Status:** Open + +## Description + +When a payer has both a pending RCA offer and a pending RCAU offer for the same `agreementId` and neither has been accepted, the order of cancellations matters. The `cancel()` overload that takes a terms hash delegates authorization to `_requirePayer()` (lines 480-497), which first checks the accepted agreement's payer and then the stored `rcaOffers` entry's payer. It does not fall back to `rcauOffers`. + +If the payer first cancels the RCA offer under `SCOPE_PENDING`, the entry in `rcaOffers` is deleted. A subsequent attempt to cancel the RCAU offer then fails: `_requirePayer()` finds no accepted agreement and no RCA offer, and reverts with `RecurringCollectorAgreementNotFound`. The orphaned RCAU offer remains in storage and unreachable by the payer. If the same parameters are later re-used to offer a new RCA, the orphaned RCAU is associated with it. The `updateNonce` check prevents immediate acceptance of the stale RCAU, but the payer has lost the ability to clean up state they own. + +## Recommended Mitigation + +Extend `_requirePayer()` to also check `rcauOffers` for a payer match when neither an accepted agreement nor an RCA offer is present. Alternatively, enforce symmetric cleanup so that deleting an RCA offer under `SCOPE_PENDING` also deletes any `rcauOffers` entry with the same `agreementId`. + +## Team Response + +TBD + +--- diff --git a/packages/issuance/audits/PR1301/TRST-L-8.md b/packages/issuance/audits/PR1301/TRST-L-8.md new file mode 100644 index 000000000..90911d2d3 --- /dev/null +++ b/packages/issuance/audits/PR1301/TRST-L-8.md @@ -0,0 +1,22 @@ +# TRST-L-8: EOA payer signatures cannot be revoked before deadline + +- **Severity:** Low +- **Category:** Functionality flaws +- **Source:** RecurringCollector.sol +- **Status:** Open + +## Description + +Payers approve agreements through two paths: an ECDSA signature consumed by `accept()` or `update()`, and a stored offer placed by a contract payer via `offer()` and consumed against the stored hash. Contract payers can revoke a pending offer by calling `cancel()` with `SCOPE_PENDING`, which deletes the matching entry from `rcaOffers` or `rcauOffers`. + +EOA payers have no equivalent revocation path. Once an RCA or RCAU has been signed, the signature is accepted by the collector at any time before the `deadline` field expires. A payer that wishes to cancel a signature-based offer before the deadline (for example, to renegotiate terms) has no mechanism to do so. The only remaining option to ensure no duplicate agreement risk is to wait out the deadline (and hope their unintended offer is not matched), or to revoke the signer via the Authorizable thawing and revocation flow, which affects all agreements authorized by that signer rather than an individual offer. + +## Recommended Mitigation + +Expose a `cancelSignature(bytes32 hash)` entry point that records the hash as invalidated on-chain, and have `_requireAuthorization()` reject any hash that has been invalidated. Alternatively, use a per-signer nonce that the payer can bump to invalidate all outstanding signatures for that signer. + +## Team Response + +TBD + +--- diff --git a/packages/issuance/audits/PR1301/TRST-L-9.md b/packages/issuance/audits/PR1301/TRST-L-9.md new file mode 100644 index 000000000..d53f195b7 --- /dev/null +++ b/packages/issuance/audits/PR1301/TRST-L-9.md @@ -0,0 +1,22 @@ +# TRST-L-9: Callback gas precheck does not account for intermediate overhead + +- **Severity:** Low +- **Category:** Gas-related issues +- **Source:** RecurringCollector.sol +- **Status:** Open + +## Description + +Both `_preCollectCallbacks()` and `_postCollectCallback()` guard each payer callback with a precheck of the form `if (gasleft() < (MAX_PAYER_CALLBACK_GAS * 64) / 63) revert`. The intent is to ensure that `MAX_PAYER_CALLBACK_GAS` remains available to the callee after applying the EIP-150 63/64 rule. + +However, the precheck is performed before the CALL or STATICCALL opcode itself, and additional gas is consumed between the comparison and the opcode: local Solidity operations, stack and memory setup, calldata encoding, and the fixed cost of the CALL or STATICCALL instruction. The actual gas forwarded to the callee can fall below `MAX_PAYER_CALLBACK_GAS`. An honest callee may perform incorrect logic under the assumption of available gas. One can refer to Optimism's CrossDomainMessenger, which adds explicit buffer constants (`RELAY_GAS_CHECK_BUFFER` and `RELAY_CALL_OVERHEAD`) for this exact reason. + +## Recommended Mitigation + +Add explicit buffer constants to the precheck so that the comparison accounts for the CALL/STATICCALL cost and the intervening Solidity overhead. Size the buffer so that at least `MAX_PAYER_CALLBACK_GAS` is forwarded to the callee when the check passes. + +## Team Response + +TBD + +--- diff --git a/packages/issuance/audits/PR1301/TRST-M-1.md b/packages/issuance/audits/PR1301/TRST-M-1.md index 6ff77952f..6b10edb96 100644 --- a/packages/issuance/audits/PR1301/TRST-M-1.md +++ b/packages/issuance/audits/PR1301/TRST-M-1.md @@ -23,7 +23,11 @@ Add a minimum thaw threshold in `_updateEscrow()`. Amounts below the threshold s ## Team Response -TBD +Fixed. + +## Mitigation Review + +The griefing path remains reachable. Before any agreement is offered, a 1 wei donation to the (collector, provider) escrow account, followed by a permissionless call to `_reconcilePairTracking()` reaches `_updateEscrow()` with min and max at zero, and the thaw threshold is also at zero. Any positive excess passes the `thawThreshold <= excess` check, causing an `adjustThaw(thawTarget = 1)`. The same sequence also occurs after the final collection of an agreement, when `sumMaxNextClaim` transitions to zero via `afterCollection()` -> `_reconcileAndUpdateEscrow()` -> `_reconcileAgreement()`. There should be a nominal, non-negligible minimum thaw amount on top of the fraction check, applied in both `_reconcileProviderEscrow()` and `_withdrawAndRebalance()`. When `escrowBasis` is JustInTime, override the nominal skip so that dust can still be thawed out for solvency. --- diff --git a/packages/issuance/audits/PR1301/TRST-M-2.md b/packages/issuance/audits/PR1301/TRST-M-2.md index 9fc633fa5..df5ca47c6 100644 --- a/packages/issuance/audits/PR1301/TRST-M-2.md +++ b/packages/issuance/audits/PR1301/TRST-M-2.md @@ -3,7 +3,7 @@ - **Severity:** Medium - **Category:** Logical flaw - **Source:** RecurringAgreementManager.sol -- **Status:** Open +- **Status:** Fixed ## Description @@ -19,7 +19,11 @@ The original intention cannot be truly fulfilled without major redesign of multi ## Team Response -TBD +Fixed. + +## Mitigation Review + +The new setup is schematically sound. Admin intervention to trigger JustInTime may still be required to satisfy requests when the system is in OnDemand but insufficient liquidity is being thawed or minted into the contract. --- diff --git a/packages/issuance/audits/PR1301/TRST-M-3.md b/packages/issuance/audits/PR1301/TRST-M-3.md index ea3c6f7da..7654bbe6c 100644 --- a/packages/issuance/audits/PR1301/TRST-M-3.md +++ b/packages/issuance/audits/PR1301/TRST-M-3.md @@ -3,7 +3,7 @@ - **Severity:** Medium - **Category:** Logical flaw - **Source:** RecurringAgreementManager.sol -- **Status:** Open +- **Status:** Acknowledged ## Description @@ -19,7 +19,7 @@ Add a separate configuration flag (e.g., `allowModeDegradation`) that must be ex ## Team Response -TBD +Acknowledged. The risk is documented, including the operator caution about pre-offer headroom checks. --- diff --git a/packages/issuance/audits/PR1301/TRST-M-4.md b/packages/issuance/audits/PR1301/TRST-M-4.md new file mode 100644 index 000000000..4da7a926a --- /dev/null +++ b/packages/issuance/audits/PR1301/TRST-M-4.md @@ -0,0 +1,24 @@ +# TRST-M-4: Returndata bombing via payer callbacks in \_preCollectCallbacks and \_postCollectCallback + +- **Severity:** Medium +- **Category:** Gas-related issues +- **Source:** RecurringCollector.sol +- **Status:** Open + +## Description + +All three payer callbacks reachable from `_collect()` (the eligibility staticcall in `_preCollectCallbacks()` at line 633, the `beforeCollection()` call in the same function at line 646, and the `afterCollection()` call in `_postCollectCallback()` at line 666) use Solidity's default low-level call pattern, which copies the full returndata buffer into the caller's memory. Note that RETURNDATACOPY is emitted even when the returned bytes are discarded via the `(bool ok, )` tuple pattern. + +With a forwarded budget of `MAX_PAYER_CALLBACK_GAS` (1,500,000) per callback, a malicious payer can expand callee memory and return roughly 850 KB of data. The caller's RETURNDATACOPY and the associated memory expansion then consume approximately 1,500,000 gas in the `_collect()` frame for each callback. Across the three callbacks, a single `collect()` call can be forced to burn about 4,500,000 gas beyond the nominal callback budget. + +The impact is an inflated collection cost that is not reflected in off-chain gas estimates. This is gas griefing rather than a collection block, and gas costs remain manageable. + +## Recommended Mitigation + +Replace the affected high-level call sites with inline assembly that performs the call and bounds the amount of returndata copied. For the eligibility check, copy at most 32 bytes into scratch memory and read the result. For `beforeCollection()` and `afterCollection()`, copy zero bytes since the return value is unused. + +## Team Response + +TBD + +--- diff --git a/packages/issuance/audits/PR1301/TRST-M-5.md b/packages/issuance/audits/PR1301/TRST-M-5.md new file mode 100644 index 000000000..34890fba2 --- /dev/null +++ b/packages/issuance/audits/PR1301/TRST-M-5.md @@ -0,0 +1,24 @@ +# TRST-M-5: Perpetual thaw griefing via micro deposits in \_reconcileProviderEscrow + +- **Severity:** Medium +- **Category:** Griefing attacks +- **Source:** RecurringAgreementManager.sol +- **Status:** Open + +## Description + +The `_reconcileProviderEscrow()` and symmetrically `_withdrawAndRebalance()` functions compare the escrow excess against a fraction-based threshold derived from `sumMaxNextClaim`. The check is structured as `thawThreshold <= excess`, which permits a thaw whenever the cumulative excess is at least the threshold. Because the threshold is keyed on `sumMaxNextClaim` and not on the amount being added to `thawingTarget` in the current round, the check behaves like a one-time gate rather than a per-round qualifier. + +An attacker can grief the RAM in two phases. First, they make a single non-negligible donation via the permissionless `PaymentsEscrow.depositTo()` that pushes the escrow balance for a (collector, provider) pair above `initial_excess > thawThreshold`. This bootstrap round costs the attacker an amount on the order of the threshold and triggers the initial `adjustThaw()` call, starting the thaw timer with `thawingTarget = initial_excess`. Second, the attacker repeatedly donates 1 wei and triggers reconciliation. The bootstrap excess is still present, so `excess > thawThreshold` continues to hold. Each round passes the check, calls `adjustThaw()` with `thawingTarget` incremented by 1 wei, and resets the thaw timer. Legitimate larger thaws issued by the RAM while the griefing is active are blocked for the duration of the thawing period because the timer keeps resetting. + +The per-round cost to the attacker after the bootstrap is 1 wei plus gas. The griefing causes spurious thaws, consumes gas on every reconciliation, and interacts with `PaymentsEscrow.adjustThaw()` timer semantics to indefinitely delay legitimate thaws for the targeted pair. + +## Recommended Mitigation + +Gate the check on the incremental amount being added to `thawingTarget` in the current round rather than on the cumulative excess over the maximum. A round should only pass the threshold check when the new delta to `thawingTarget` is non-trivial. Combine this with an absolute nominal minimum thaw amount applied in both `_reconcileProviderEscrow()` and `_withdrawAndRebalance()` so that sub-nominal dust increments cannot reset the thaw timer even after the bootstrap. + +## Team Response + +TBD + +--- diff --git a/packages/issuance/audits/PR1301/TRST-R-10.md b/packages/issuance/audits/PR1301/TRST-R-10.md new file mode 100644 index 000000000..219698e5f --- /dev/null +++ b/packages/issuance/audits/PR1301/TRST-R-10.md @@ -0,0 +1,7 @@ +# TRST-R-10: Document role-change semantics for existing agreements + +- **Severity:** Recommendation + +## Description + +Changes to `DATA_SERVICE_ROLE` and `COLLECTOR_ROLE` on the RecurringAgreementManager do not affect agreements that have already been offered or accepted through the previously authorized addresses. This is by design (revoking a role should not invalidate settled obligations), but the behavior is not documented. Record this invariant in the RAM documentation so that operators and integrators understand the effect of role changes. diff --git a/packages/issuance/audits/PR1301/TRST-R-11.md b/packages/issuance/audits/PR1301/TRST-R-11.md new file mode 100644 index 000000000..014f20625 --- /dev/null +++ b/packages/issuance/audits/PR1301/TRST-R-11.md @@ -0,0 +1,7 @@ +# TRST-R-11: Remove or implement unused state flags in IAgreementCollector + +- **Severity:** Recommendation + +## Description + +`IAgreementCollector` defines state flag constants that are not currently used in the RecurringCollector implementation, including `NOTICE_GIVEN`, `SETTLED`, `BY_PAYER`, `BY_PROVIDER`, `BY_DATA_SERVICE`, `AUTO_UPDATE`, and `AUTO_UPDATED`. Unused public interface constants are a source of confusion for integrators, who may code against documented semantics that the implementation does not honor. Either remove the unused flags from the interface, or implement the behaviors they describe in the collector. diff --git a/packages/issuance/audits/PR1301/TRST-R-12.md b/packages/issuance/audits/PR1301/TRST-R-12.md new file mode 100644 index 000000000..a73ed9648 --- /dev/null +++ b/packages/issuance/audits/PR1301/TRST-R-12.md @@ -0,0 +1,7 @@ +# TRST-R-12: Document ACCEPTED state returned for cancelled agreements + +- **Severity:** Recommendation + +## Description + +In `getAgreementDetails()`, any agreement whose state is not `AgreementState.NotAccepted` is reported with state flag `ACCEPTED`. This includes agreements that have been cancelled (`CanceledByPayer` or `CanceledByServiceProvider`). Integrators inspecting the returned state cannot distinguish cancelled agreements from live ones without reading separate storage. Document this behavior in the interface, or extend the state bitmask with a `CANCELED` flag and return it for the non-active terminal states. diff --git a/packages/issuance/audits/PR1301/TRST-R-13.md b/packages/issuance/audits/PR1301/TRST-R-13.md new file mode 100644 index 000000000..6b9b090c0 --- /dev/null +++ b/packages/issuance/audits/PR1301/TRST-R-13.md @@ -0,0 +1,7 @@ +# TRST-R-13: Document reclaim reason change for stale allocation force-close + +- **Severity:** Recommendation + +## Description + +Before the PR's refactor, `forceCloseStaleAllocation()` closed the allocation via `_closeAllocation()` and caused a reclaim with reason `CLOSE_ALLOCATION`. Post refactor, the force close path goes through `_resizeAllocation(allocationId, 0, ...)`, which triggers a reclaim with reason `STALE_POI` instead. The reclaim still occurs, but the reason code exposed to reclaim address configuration changes. Document this change so that operators are able to prepare accordingly and have funding paths line up with intention. diff --git a/packages/issuance/audits/PR1301/TRST-R-5.md b/packages/issuance/audits/PR1301/TRST-R-5.md new file mode 100644 index 000000000..f3d5ac72e --- /dev/null +++ b/packages/issuance/audits/PR1301/TRST-R-5.md @@ -0,0 +1,7 @@ +# TRST-R-5: Ambiguous return value in getAgreementOfferAt() + +- **Severity:** Recommendation + +## Description + +`getAgreementOfferAt()` returns `(uint8 offerType, bytes memory offerData)`. The offer type constant `OFFER_TYPE_NEW` is defined as 0, which is also the default Solidity return value when no stored offer exists for the given `agreementId` and index. A caller receiving `offerType == 0` cannot distinguish between a stored new-type offer existing and no offer existing. Consider redefining offer type constants with 1-indexed values, or adding an explicit `bool found` return parameter. diff --git a/packages/issuance/audits/PR1301/TRST-R-6.md b/packages/issuance/audits/PR1301/TRST-R-6.md new file mode 100644 index 000000000..9fa653c5f --- /dev/null +++ b/packages/issuance/audits/PR1301/TRST-R-6.md @@ -0,0 +1,7 @@ +# TRST-R-6: Dead code guard in \_validateAndStoreUpdate() + +- **Severity:** Recommendation + +## Description + +In `_validateAndStoreUpdate()` (line 855), the guard `if (oldHash != bytes32(0))` is unreachable as a false branch. Only agreements in the Accepted state may be updated, and every accepted agreement has a non-zero `activeTermsHash` written during `accept()` or a prior `update()`. The guard can be removed or converted into an invariant comment documenting this assumption. diff --git a/packages/issuance/audits/PR1301/TRST-R-7.md b/packages/issuance/audits/PR1301/TRST-R-7.md new file mode 100644 index 000000000..903eaaea7 --- /dev/null +++ b/packages/issuance/audits/PR1301/TRST-R-7.md @@ -0,0 +1,7 @@ +# TRST-R-7: Remove consumed offers in accept() and update() + +- **Severity:** Recommendation + +## Description + +After `accept()` or `update()` consumes a stored offer, the corresponding entry in `rcaOffers` or `rcauOffers` becomes stale. Currently only `_validateAndStoreUpdate()` cleans up the previously active offer by looking up the old `activeTermsHash`; the offer whose terms were just accepted is not deleted. This is a storage hygiene concern: stale offer entries remain in storage indefinitely until explicitly replaced or matched by a future update. Consider deleting the consumed offer entry inside `accept()` and `update()` after it has been applied. diff --git a/packages/issuance/audits/PR1301/TRST-R-8.md b/packages/issuance/audits/PR1301/TRST-R-8.md new file mode 100644 index 000000000..dd2ea9619 --- /dev/null +++ b/packages/issuance/audits/PR1301/TRST-R-8.md @@ -0,0 +1,7 @@ +# TRST-R-8: Align pause documentation with callback behavior in the RAM + +- **Severity:** Recommendation + +## Description + +The RecurringAgreementManager documentation header states that pausing the contract "stops all permissionless escrow management". In practice, the `whenNotPaused` modifier also applies to `beforeCollection()` and `afterCollection()`, so pause also halts the callback path used during `collect()`. Update the documentation to reflect that callbacks are affected, or narrow the modifier application so that behavior matches the prose. diff --git a/packages/issuance/audits/PR1301/TRST-R-9.md b/packages/issuance/audits/PR1301/TRST-R-9.md new file mode 100644 index 000000000..b78e271fe --- /dev/null +++ b/packages/issuance/audits/PR1301/TRST-R-9.md @@ -0,0 +1,7 @@ +# TRST-R-9: \_isAuthorized() override in RecurringCollector trusts itself for any authorizer + +- **Severity:** Recommendation + +## Description + +The `_isAuthorized(address authorizer, address signer)` override in RecurringCollector returns true whenever `signer == address(this)`, regardless of `authorizer`. This enables RecurringCollector to call `dataService.cancelIndexingAgreementByPayer()` on the payer's behalf. The semantics are safe in the current integration with SubgraphService, but they widen the trust surface: any future consumer that relies on `RecurringCollector.isAuthorized()` for access control will grant access when the signer is the collector itself. Consider tightening the override to scope trust to specific callers, or explicitly document the integration contract so it is not misapplied by future consumers. diff --git a/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/cancel.t.sol b/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/cancel.t.sol index 0b5463cd4..3a8d0340f 100644 --- a/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/cancel.t.sol +++ b/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/cancel.t.sol @@ -250,9 +250,7 @@ contract SubgraphServiceIndexingAgreementCancelTest is SubgraphServiceIndexingAg // solhint-disable-next-line graph/func-name-mixedcase /// @notice An indexer whose provision drops below minimum should still be able /// to cancel their indexing agreement. Cancel is an exit path. - function test_SubgraphService_CancelIndexingAgreement_OK_WhenProvisionBelowMinimum( - Seed memory seed - ) public { + function test_SubgraphService_CancelIndexingAgreement_OK_WhenProvisionBelowMinimum(Seed memory seed) public { Context storage ctx = _newCtx(seed); IndexerState memory indexerState = _withIndexer(ctx); ( From 3ceb156dea4ef7906f4a2dfaf8ce55d798831589 Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Fri, 17 Apr 2026 09:15:16 +0000 Subject: [PATCH 02/31] fix(collector): add gas overhead buffer to callback prechecks (TRST-L-9) The gasleft() prechecks only accounted for EIP-150's 63/64 rule but not the gas consumed between the check and the CALL opcode. Add a CALLBACK_GAS_OVERHEAD constant (3,000 gas) to all three prechecks so at least MAX_PAYER_CALLBACK_GAS is forwarded to the callee. --- .../collectors/RecurringCollector.sol | 15 ++++- .../recurring-collector/afterCollection.t.sol | 64 ++++++++++++++++++- packages/issuance/audits/PR1301/TRST-L-9.md | 11 ++++ 3 files changed, 86 insertions(+), 4 deletions(-) diff --git a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol index 171e6e8f0..c51d79d44 100644 --- a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol +++ b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol @@ -58,6 +58,12 @@ contract RecurringCollector is /// that could starve the core collect() call of gas. uint256 private constant MAX_PAYER_CALLBACK_GAS = 1_500_000; + /// @notice Gas overhead between the gasleft() precheck and the actual CALL/STATICCALL opcode. + /// Covers ABI encoding, stack/memory setup, and the CALL base cost so that at least + /// MAX_PAYER_CALLBACK_GAS is forwarded to the callee. Sized to cover the cold-account + /// EIP-2929 access cost (2_600) plus Solidity framing. + uint256 private constant CALLBACK_GAS_OVERHEAD = 3_000; + /* solhint-disable gas-small-strings */ /// @notice The EIP712 typehash for the RecurringCollectionAgreement struct bytes32 public constant EIP712_RCA_TYPEHASH = @@ -730,7 +736,8 @@ contract RecurringCollector is if ((agreement.conditions & CONDITION_ELIGIBILITY_CHECK) != 0) { // 64/63 accounts for EIP-150 63/64 gas forwarding rule. - if (gasleft() < (MAX_PAYER_CALLBACK_GAS * 64) / 63) revert RecurringCollectorInsufficientCallbackGas(); + if (gasleft() < (MAX_PAYER_CALLBACK_GAS * 64) / 63 + CALLBACK_GAS_OVERHEAD) + revert RecurringCollectorInsufficientCallbackGas(); // Eligibility gate (opt-in via conditions bitmask): low-level staticcall avoids // caller-side ABI decode reverts. Only an explicit return of 0 blocks collection; @@ -747,7 +754,8 @@ contract RecurringCollector is } if (payer.code.length != 0 && payer != msg.sender) { - if (gasleft() < (MAX_PAYER_CALLBACK_GAS * 64) / 63) revert RecurringCollectorInsufficientCallbackGas(); + if (gasleft() < (MAX_PAYER_CALLBACK_GAS * 64) / 63 + CALLBACK_GAS_OVERHEAD) + revert RecurringCollectorInsufficientCallbackGas(); // solhint-disable-next-line avoid-low-level-calls (bool beforeOk, ) = payer.call{ gas: MAX_PAYER_CALLBACK_GAS }( @@ -768,7 +776,8 @@ contract RecurringCollector is // Notify contract payers so they can reconcile escrow in the same transaction. if (payer != msg.sender && payer.code.length != 0) { // 64/63 accounts for EIP-150 63/64 gas forwarding rule. - if (gasleft() < (MAX_PAYER_CALLBACK_GAS * 64) / 63) revert RecurringCollectorInsufficientCallbackGas(); + if (gasleft() < (MAX_PAYER_CALLBACK_GAS * 64) / 63 + CALLBACK_GAS_OVERHEAD) + revert RecurringCollectorInsufficientCallbackGas(); // solhint-disable-next-line avoid-low-level-calls (bool afterOk, ) = payer.call{ gas: MAX_PAYER_CALLBACK_GAS }( abi.encodeCall(IAgreementOwner.afterCollection, (agreementId, tokensToCollect)) diff --git a/packages/horizon/test/unit/payments/recurring-collector/afterCollection.t.sol b/packages/horizon/test/unit/payments/recurring-collector/afterCollection.t.sol index 3e7396178..90ae638e7 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/afterCollection.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/afterCollection.t.sol @@ -143,7 +143,7 @@ contract RecurringCollectorAfterCollectionTest is RecurringCollectorSharedTest { ); // Binary-search for a gas limit that passes core collect logic but trips the - // callback gas guard (gasleft < MAX_PAYER_CALLBACK_GAS * 64/63 ≈ 1_523_810). + // callback gas guard (gasleft < MAX_PAYER_CALLBACK_GAS * 64/63 + CALLBACK_GAS_OVERHEAD ≈ 1_526_810). // Core logic + escrow call + beforeCollection + events uses ~200k gas. bool triggered; for (uint256 gasLimit = 1_700_000; gasLimit > 1_500_000; gasLimit -= 10_000) { @@ -166,6 +166,68 @@ contract RecurringCollectorAfterCollectionTest is RecurringCollectorSharedTest { assertTrue(triggered, "Should have triggered InsufficientCallbackGas at some gas limit"); } + /// @notice TRST-L-9: the CALLBACK_GAS_OVERHEAD precheck also guards the eligibility staticcall + /// (first of three callback prechecks). Binary-search for a gas limit that reaches the + /// eligibility precheck and trips it, confirming the buffer logic applies there too. + function test_Collect_Revert_WhenInsufficientCallbackGas_EligibilityPrecheck() public { + MockAgreementOwner approver = _newApprover(); + IRecurringCollector.RecurringCollectionAgreement memory rca = _recurringCollectorHelper.sensibleRCA( + IRecurringCollector.RecurringCollectionAgreement({ + deadline: uint64(block.timestamp + 1 hours), + endsAt: uint64(block.timestamp + 365 days), + payer: address(approver), + dataService: makeAddr("ds"), + serviceProvider: makeAddr("sp"), + maxInitialTokens: 100 ether, + maxOngoingTokensPerSecond: 1 ether, + minSecondsPerCollection: 600, + maxSecondsPerCollection: 3600, + conditions: 0, + nonce: 1, + metadata: "" + }) + ); + rca.conditions = 1; // CONDITION_ELIGIBILITY_CHECK — activates the eligibility precheck first + + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_NEW, abi.encode(rca), 0); + _setupValidProvision(rca.serviceProvider, rca.dataService); + + vm.prank(rca.dataService); + bytes16 agreementId = _recurringCollector.accept(rca, ""); + + skip(rca.minSecondsPerCollection); + uint256 tokens = 1 ether; + bytes memory data = _generateCollectData(_generateCollectParams(rca, agreementId, bytes32("col1"), tokens, 0)); + bytes memory callData = abi.encodeCall( + _recurringCollector.collect, + (IGraphPayments.PaymentTypes.IndexingFee, data) + ); + + // With eligibility enabled, three sequential callbacks each need the buffer. The test + // confirms at least one (the first, eligibility) trips InsufficientCallbackGas. + bool triggered; + for (uint256 gasLimit = 1_700_000; gasLimit > 1_500_000; gasLimit -= 10_000) { + uint256 snap = vm.snapshot(); + vm.prank(rca.dataService); + (bool success, bytes memory returnData) = address(_recurringCollector).call{ gas: gasLimit }(callData); + if (!success && returnData.length >= 4) { + bytes4 selector; + // solhint-disable-next-line no-inline-assembly + assembly { + selector := mload(add(returnData, 32)) + } + if (selector == IRecurringCollector.RecurringCollectorInsufficientCallbackGas.selector) { + triggered = true; + assertTrue(vm.revertTo(snap)); + break; + } + } + assertTrue(vm.revertTo(snap)); + } + assertTrue(triggered, "eligibility precheck must trip InsufficientCallbackGas under tight gas"); + } + function test_AfterCollection_NotCalledForEOAPayer(FuzzyTestCollect calldata fuzzy) public { // Use standard ECDSA-signed path (EOA payer, no contract) (IRecurringCollector.RecurringCollectionAgreement memory acceptedRca, , , ) = _sensibleAuthorizeAndAccept( diff --git a/packages/issuance/audits/PR1301/TRST-L-9.md b/packages/issuance/audits/PR1301/TRST-L-9.md index d53f195b7..e98f66046 100644 --- a/packages/issuance/audits/PR1301/TRST-L-9.md +++ b/packages/issuance/audits/PR1301/TRST-L-9.md @@ -20,3 +20,14 @@ Add explicit buffer constants to the precheck so that the comparison accounts fo TBD --- + +Added `CALLBACK_GAS_OVERHEAD = 3_000` constant. All three prechecks now use: + +```solidity +if (gasleft() < (MAX_PAYER_CALLBACK_GAS * 64) / 63 + CALLBACK_GAS_OVERHEAD) + revert RecurringCollectorInsufficientCallbackGas(); +``` + +Sized to cover the worst-case pre-opcode cost. The eligibility STATICCALL is the first access to the payer account on the collect path, so the EIP-2929 cold-account access cost (2_600) dominates; the remaining headroom covers `abi.encodeCall` and stack/memory setup. Subsequent `beforeCollection` / `afterCollection` calls hit the payer warm (100 gas access), so the buffer is generous there. + +Follows the Optimism buffer-constant pattern as suggested. From 35f34564a807f96a22b7b1983807db5b4491db5d Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Fri, 17 Apr 2026 09:34:45 +0000 Subject: [PATCH 03/31] fix(collector): cap returndata copy in payer callbacks (TRST-M-4) Replace Solidity low-level calls with inline assembly to bound returndata copy: eligibility staticcall copies at most 32 bytes, beforeCollection/afterCollection copy zero bytes. Prevents a malicious payer from forcing ~4.5M gas overhead via returndata bombing. --- .../collectors/RecurringCollector.sol | 54 +++++---- .../recurring-collector/returndataBomb.t.sol | 108 ++++++++++++++++++ packages/issuance/audits/PR1301/TRST-M-4.md | 9 ++ 3 files changed, 149 insertions(+), 22 deletions(-) create mode 100644 packages/horizon/test/unit/payments/recurring-collector/returndataBomb.t.sol diff --git a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol index c51d79d44..2cfb38767 100644 --- a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol +++ b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol @@ -731,36 +731,40 @@ contract RecurringCollector is ) private { address payer = agreement.payer; address provider = agreement.serviceProvider; - // Payer callbacks use gas-capped low-level calls to prevent gas siphoning and - // caller-side ABI decode reverts. Failures emit events but do not block collection. + // Eligibility gate (opt-in via conditions bitmask). Assembly staticcall caps returndata + // copy to 32 bytes, preventing returndata bombing. Only an explicit return of 0 blocks + // collection; reverts, short returndata, and malformed responses are treated as "no + // opinion" (collection proceeds). if ((agreement.conditions & CONDITION_ELIGIBILITY_CHECK) != 0) { - // 64/63 accounts for EIP-150 63/64 gas forwarding rule. if (gasleft() < (MAX_PAYER_CALLBACK_GAS * 64) / 63 + CALLBACK_GAS_OVERHEAD) revert RecurringCollectorInsufficientCallbackGas(); - - // Eligibility gate (opt-in via conditions bitmask): low-level staticcall avoids - // caller-side ABI decode reverts. Only an explicit return of 0 blocks collection; - // reverts, short returndata, and malformed responses are treated as "no opinion" - // (collection proceeds). - // solhint-disable-next-line avoid-low-level-calls - (bool success, bytes memory result) = payer.staticcall{ gas: MAX_PAYER_CALLBACK_GAS }( - abi.encodeCall(IProviderEligibility.isEligible, (provider)) - ); - if (success && !(result.length < 32) && abi.decode(result, (uint256)) == 0) + bytes memory cd = abi.encodeCall(IProviderEligibility.isEligible, (provider)); + bool success; + uint256 returnLen; + uint256 result; + // solhint-disable-next-line no-inline-assembly + assembly { + success := staticcall(MAX_PAYER_CALLBACK_GAS, payer, add(cd, 0x20), mload(cd), 0x00, 0x20) + returnLen := returndatasize() + result := mload(0x00) + } + if (success && !(returnLen < 32) && result == 0) revert RecurringCollectorCollectionNotEligible(agreementId, provider); - if (!success || result.length < 32) + if (!success || returnLen < 32) emit PayerCallbackFailed(agreementId, payer, PayerCallbackStage.EligibilityCheck); } + // Assembly call copies 0 bytes of returndata, preventing returndata bombing. if (payer.code.length != 0 && payer != msg.sender) { if (gasleft() < (MAX_PAYER_CALLBACK_GAS * 64) / 63 + CALLBACK_GAS_OVERHEAD) revert RecurringCollectorInsufficientCallbackGas(); - - // solhint-disable-next-line avoid-low-level-calls - (bool beforeOk, ) = payer.call{ gas: MAX_PAYER_CALLBACK_GAS }( - abi.encodeCall(IAgreementOwner.beforeCollection, (agreementId, tokensToCollect)) - ); + bytes memory cd = abi.encodeCall(IAgreementOwner.beforeCollection, (agreementId, tokensToCollect)); + bool beforeOk; + // solhint-disable-next-line no-inline-assembly + assembly { + beforeOk := call(MAX_PAYER_CALLBACK_GAS, payer, 0, add(cd, 0x20), mload(cd), 0, 0) + } if (!beforeOk) emit PayerCallbackFailed(agreementId, payer, PayerCallbackStage.BeforeCollection); } } @@ -778,10 +782,16 @@ contract RecurringCollector is // 64/63 accounts for EIP-150 63/64 gas forwarding rule. if (gasleft() < (MAX_PAYER_CALLBACK_GAS * 64) / 63 + CALLBACK_GAS_OVERHEAD) revert RecurringCollectorInsufficientCallbackGas(); - // solhint-disable-next-line avoid-low-level-calls - (bool afterOk, ) = payer.call{ gas: MAX_PAYER_CALLBACK_GAS }( - abi.encodeCall(IAgreementOwner.afterCollection, (agreementId, tokensToCollect)) + // Assembly call copies 0 bytes of returndata, preventing returndata bombing. + bytes memory afterCallData = abi.encodeCall( + IAgreementOwner.afterCollection, + (agreementId, tokensToCollect) ); + bool afterOk; + // solhint-disable-next-line no-inline-assembly + assembly { + afterOk := call(MAX_PAYER_CALLBACK_GAS, payer, 0, add(afterCallData, 0x20), mload(afterCallData), 0, 0) + } if (!afterOk) emit PayerCallbackFailed(agreementId, payer, PayerCallbackStage.AfterCollection); } } diff --git a/packages/horizon/test/unit/payments/recurring-collector/returndataBomb.t.sol b/packages/horizon/test/unit/payments/recurring-collector/returndataBomb.t.sol new file mode 100644 index 000000000..3ef69f430 --- /dev/null +++ b/packages/horizon/test/unit/payments/recurring-collector/returndataBomb.t.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import { IRecurringCollector } from "@graphprotocol/interfaces/contracts/horizon/IRecurringCollector.sol"; +import { IGraphPayments } from "@graphprotocol/interfaces/contracts/horizon/IGraphPayments.sol"; +import { IAgreementOwner } from "@graphprotocol/interfaces/contracts/horizon/IAgreementOwner.sol"; +import { IProviderEligibility } from "@graphprotocol/interfaces/contracts/issuance/eligibility/IProviderEligibility.sol"; +import { OFFER_TYPE_NEW } from "@graphprotocol/interfaces/contracts/horizon/IAgreementCollector.sol"; +import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + +import { RecurringCollectorSharedTest } from "./shared.t.sol"; + +/// @notice Payer that returns a configurable-size buffer from every callback. +/// Used to verify the collector caps returndata copy into its outer frame. +contract HugeReturnPayer is IAgreementOwner, IERC165 { + uint256 public returnBytes = 500_000; + + function setReturnBytes(uint256 size) external { + returnBytes = size; + } + + function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { + return interfaceId == type(IERC165).interfaceId || interfaceId == type(IProviderEligibility).interfaceId; + } + + function beforeCollection(bytes16, uint256) external { + uint256 size = returnBytes; + // solhint-disable-next-line no-inline-assembly + assembly { + return(0, size) + } + } + + function afterCollection(bytes16, uint256) external { + uint256 size = returnBytes; + // solhint-disable-next-line no-inline-assembly + assembly { + return(0, size) + } + } + + /// @notice isEligible — first 32 bytes = 1 (eligible), remainder is memory-expansion padding. + fallback() external { + uint256 size = returnBytes; + // solhint-disable-next-line no-inline-assembly + assembly { + mstore(0, 1) + return(0, size) + } + } +} + +contract RecurringCollectorReturndataBombTest is RecurringCollectorSharedTest { + /* solhint-disable graph/func-name-mixedcase */ + + /// @notice All three payer callbacks return 500KB. With bounded retSize at each call site + /// the outer frame does not copy the returndata, so gas usage stays proportional to the + /// callbacks' own internal work. Without the bound, the outer frame incurs memory expansion + /// + RETURNDATACOPY for each 500KB payload, roughly doubling gas consumption. + function test_Collect_BoundsReturndataCopy_WhenPayerReturnsHuge() public { + HugeReturnPayer attacker = new HugeReturnPayer(); + attacker.setReturnBytes(500_000); + + IRecurringCollector.RecurringCollectionAgreement memory rca = _recurringCollectorHelper.sensibleRCA( + IRecurringCollector.RecurringCollectionAgreement({ + deadline: uint64(block.timestamp + 1 hours), + endsAt: uint64(block.timestamp + 365 days), + payer: address(attacker), + dataService: makeAddr("ds"), + serviceProvider: makeAddr("sp"), + maxInitialTokens: 100 ether, + maxOngoingTokensPerSecond: 1 ether, + minSecondsPerCollection: 600, + maxSecondsPerCollection: 3600, + conditions: 0, + nonce: 1, + metadata: "" + }) + ); + rca.conditions = 1; // CONDITION_ELIGIBILITY_CHECK — exercise the eligibility staticcall path + + vm.prank(address(attacker)); + _recurringCollector.offer(OFFER_TYPE_NEW, abi.encode(rca), 0); + _setupValidProvision(rca.serviceProvider, rca.dataService); + + vm.prank(rca.dataService); + bytes16 agreementId = _recurringCollector.accept(rca, ""); + + skip(rca.minSecondsPerCollection); + uint256 tokens = 1 ether; + bytes memory data = _generateCollectData(_generateCollectParams(rca, agreementId, bytes32("col1"), tokens, 0)); + + uint256 gasBefore = gasleft(); + vm.prank(rca.dataService); + uint256 collected = _recurringCollector.collect(IGraphPayments.PaymentTypes.IndexingFee, data); + uint256 gasUsed = gasBefore - gasleft(); + + assertEq(collected, tokens, "collect should succeed despite huge returndata"); + + // Bounded frame: base collect (~200k) plus three callbacks' internal 500KB expansion + // (~520k each) totals roughly 1.8M. Without the bound each callback additionally causes + // ~520k of outer-frame memory expansion plus the RETURNDATACOPY itself, pushing the + // total above 3.3M. A 2.5M ceiling cleanly separates the two cases. + assertLt(gasUsed, 2_500_000, "outer frame consumed unbounded payer returndata"); + } + + /* solhint-enable graph/func-name-mixedcase */ +} diff --git a/packages/issuance/audits/PR1301/TRST-M-4.md b/packages/issuance/audits/PR1301/TRST-M-4.md index 4da7a926a..ad06eac0e 100644 --- a/packages/issuance/audits/PR1301/TRST-M-4.md +++ b/packages/issuance/audits/PR1301/TRST-M-4.md @@ -22,3 +22,12 @@ Replace the affected high-level call sites with inline assembly that performs th TBD --- + +## Fix + +Replaced all three call sites with inline assembly that bounds returndata copy: + +- **Eligibility staticcall**: copies at most 32 bytes into scratch space (0x00), reads the `uint256` result from there. +- **beforeCollection / afterCollection**: copy 0 bytes (`retSize=0`), only the `bool success` from the CALL opcode is used. + +This prevents a malicious payer from forcing RETURNDATACOPY of ~850 KB per callback. From 46c3d94e5b577032ebf285501bc5f9668eda25d4 Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Fri, 17 Apr 2026 09:50:52 +0000 Subject: [PATCH 04/31] docs: add response to TRST-L-10 EIP-7702 callback dispatch (won't fix) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CONDITION_ELIGIBILITY_CHECK is an agreement term, not a proxy for payer type — contract payers can legitimately offer agreements without it, and gating callback dispatch on the flag would deny beforeCollection / afterCollection to those payers. With the M-4 returndata-bombing fix in place, the gas impact of an EIP-7702 EOA acquiring callbacks is bounded and predictable, and the callbacks themselves are non-reverting and non-blocking. --- packages/issuance/audits/PR1301/TRST-L-10.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/issuance/audits/PR1301/TRST-L-10.md b/packages/issuance/audits/PR1301/TRST-L-10.md index 385b4ee0c..919cda91d 100644 --- a/packages/issuance/audits/PR1301/TRST-L-10.md +++ b/packages/issuance/audits/PR1301/TRST-L-10.md @@ -20,3 +20,7 @@ Use the introduced `CONDITION_ELIGIBILITY_CHECK` flag in place of the live `code TBD --- + +Using `CONDITION_ELIGIBILITY_CHECK` for callback dispatch does not seem appropriate. The eligibility check is an agreement term, not a proxy for payer type and contract payers can legitimately offer agreements without this condition. The provider agreeing to the check requires greater trust in the payer. Gating callbacks on this flag would deny `beforeCollection`/`afterCollection` to contract payers for agreements without eligibility gating. + +With the returndata bombing fix (TRST-M-4), the gas impact of an EIP-7702 EOA gaining callbacks is bounded and predictable. We do not believe this as a significant attack vector. The `beforeCollection`/`afterCollection` callbacks are non-reverting and non-blocking. A payer adding code via EIP-7702 to better handle escrow reconciliation could be a valid use case and in the best interests of all parties. From 5e2fa0ac83fd16abdde5ef6f97028b1c812ff884 Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Fri, 17 Apr 2026 12:13:24 +0000 Subject: [PATCH 05/31] feat(RAM): drop pair tracking below residual escrow threshold (TRST-M-1, TRST-M-5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add minResidualEscrowFactor (uint8, default 50 → threshold 2^50 wei ≈ 0.001 GRT) to RecurringAgreementManager. When a (collector, provider) pair has no remaining agreements and the escrow balance is below the threshold, tracking is dropped — the residual is not worth the gas cost of further thaw/withdraw cycles. For untracked pairs, reconcileProvider performs a blind drain (withdraw matured thaw, thaw remainder) without re-creating tracking state. New agreements for the same pair re-add tracking naturally via _offerAgreement. --- .../payments/recurring-collector/cancel.t.sol | 2 + .../agreement/IRecurringAgreements.sol | 7 + .../agreement/IRecurringEscrowManagement.sol | 23 ++ packages/issuance/audits/PR1301/TRST-M-1.md | 4 +- packages/issuance/audits/PR1301/TRST-M-5.md | 4 + .../agreement/RecurringAgreementManager.sol | 84 +++++- .../agreement-manager/cascadeCleanup.t.sol | 11 + .../agreement-manager/residualEscrow.t.sol | 280 ++++++++++++++++++ 8 files changed, 405 insertions(+), 10 deletions(-) create mode 100644 packages/issuance/test/unit/agreement-manager/residualEscrow.t.sol diff --git a/packages/horizon/test/unit/payments/recurring-collector/cancel.t.sol b/packages/horizon/test/unit/payments/recurring-collector/cancel.t.sol index cf1da6743..1b19a2fc8 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/cancel.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/cancel.t.sol @@ -27,6 +27,8 @@ contract RecurringCollectorCancelTest is RecurringCollectorSharedTest { IRecurringCollector.RecurringCollectionAgreement memory fuzzyRCA, uint8 unboundedCanceler ) public { + vm.assume(fuzzyRCA.dataService != _proxyAdmin); + // Generate deterministic agreement ID bytes16 agreementId = _recurringCollector.generateAgreementId( fuzzyRCA.payer, diff --git a/packages/interfaces/contracts/issuance/agreement/IRecurringAgreements.sol b/packages/interfaces/contracts/issuance/agreement/IRecurringAgreements.sol index debbff6c0..1a19f3ef8 100644 --- a/packages/interfaces/contracts/issuance/agreement/IRecurringAgreements.sol +++ b/packages/interfaces/contracts/issuance/agreement/IRecurringAgreements.sol @@ -68,6 +68,13 @@ interface IRecurringAgreements { */ function getMinThawFraction() external view returns (uint8 fraction); + /** + * @notice Minimum residual escrow factor for cleanup. + * @dev Pairs with no agreements and escrow below 2^value are dropped from tracking. + * @return value The residual escrow decimals + */ + function getMinResidualEscrowFactor() external view returns (uint8 value); + /** * @notice Get the sum of maxNextClaim across all (collector, provider) pairs * @dev Populated lazily through normal operations. diff --git a/packages/interfaces/contracts/issuance/agreement/IRecurringEscrowManagement.sol b/packages/interfaces/contracts/issuance/agreement/IRecurringEscrowManagement.sol index f19bc108b..76bca5f62 100644 --- a/packages/interfaces/contracts/issuance/agreement/IRecurringEscrowManagement.sol +++ b/packages/interfaces/contracts/issuance/agreement/IRecurringEscrowManagement.sol @@ -76,6 +76,13 @@ interface IRecurringEscrowManagement { */ event MinThawFractionSet(uint8 oldFraction, uint8 newFraction); + /** + * @notice Emitted when the minimum residual escrow is changed + * @param oldValue The previous value + * @param newValue The new value + */ + event MinResidualEscrowFactorSet(uint8 oldValue, uint8 newValue); + // solhint-enable gas-indexed-events // -- Functions -- @@ -124,4 +131,20 @@ interface IRecurringEscrowManagement { * @param fraction The numerator over 256 for the dust threshold */ function setMinThawFraction(uint8 fraction) external; + + /** + * @notice Set the minimum residual escrow factor for pair tracking cleanup. + * @dev Requires OPERATOR_ROLE. When a (collector, provider) pair has no remaining agreements + * and the escrow balance is below 2^value, tracking is dropped because the residual is not worth + * the gas cost of further thaw/withdraw cycles. Funds remain in PaymentsEscrow but are no + * longer actively managed by RAM. Higher values drop tracking more aggressively. + * + * - 0: 2^0 = 1 wei (drop only at zero balance — effectively never drop) + * - 50: 2^50 ≈ 10^15 (0.001 GRT, default) + * - 60: 2^60 ≈ 10^18 (1 GRT) + * - 255: 2^255 (always drop when no agreements remain — effectively disables residual tracking) + * + * @param value The exponent (threshold = 2^value) + */ + function setMinResidualEscrowFactor(uint8 value) external; } diff --git a/packages/issuance/audits/PR1301/TRST-M-1.md b/packages/issuance/audits/PR1301/TRST-M-1.md index 6b10edb96..72927231d 100644 --- a/packages/issuance/audits/PR1301/TRST-M-1.md +++ b/packages/issuance/audits/PR1301/TRST-M-1.md @@ -31,4 +31,6 @@ The griefing path remains reachable. Before any agreement is offered, a 1 wei do --- -Added configurable `minThawFraction` (uint8, proportion of 256, default 16 = 6.25%) that skips thaws when the excess above max is below `sumMaxNextClaim * fraction / 256` for the (collector, provider) pair. An attacker must now donate a meaningful fraction per griefing round, making such an attack both economically unattractive and less effective. +Added configurable `minThawFraction` (uint8, default 16 = 6.25% of `sumMaxNextClaim`) that skips thaws below threshold. + +The zero-threshold path when `sumMaxNextClaim = 0` is acknowledged. Timer resets do not occur (`evenIfTimerReset=false` rejects increases), so the vector is limited to postponing pair tracking cleanup via repeated dust deposits. Added `minResidualEscrowFactor` (uint8, default 50, threshold = 2^value ≈ 0.001 GRT for default): pairs with no agreements and escrow below threshold are dropped from tracking. Untracked pairs can still have escrow drained via blind thaw/withdraw on `reconcileProvider`. diff --git a/packages/issuance/audits/PR1301/TRST-M-5.md b/packages/issuance/audits/PR1301/TRST-M-5.md index 34890fba2..155efa2be 100644 --- a/packages/issuance/audits/PR1301/TRST-M-5.md +++ b/packages/issuance/audits/PR1301/TRST-M-5.md @@ -22,3 +22,7 @@ Gate the check on the incremental amount being added to `thawingTarget` in the c TBD --- + +RAM always calls `adjustThaw(..., evenIfTimerReset=false)`. When a thaw is already active, any increase to `thawingTarget` that would change `thawEndTimestamp` is silently rejected by PaymentsEscrow so the timer is never reset. The "bootstrap + repeated 1 wei" attack does not work as described? + +The actual vector is narrower: indefinite postponement of pair tracking cleanup when `sumMaxNextClaim = 0`. Addressed by `minResidualEscrowFactor` in the fix for TRST-M-1. diff --git a/packages/issuance/contracts/agreement/RecurringAgreementManager.sol b/packages/issuance/contracts/agreement/RecurringAgreementManager.sol index 881208eed..6edf82117 100644 --- a/packages/issuance/contracts/agreement/RecurringAgreementManager.sol +++ b/packages/issuance/contracts/agreement/RecurringAgreementManager.sol @@ -143,7 +143,11 @@ contract RecurringAgreementManager is /** * @notice Per-(collector, provider) pair tracking data * @param sumMaxNextClaim Sum of maxNextClaim for all agreements in this pair - * @param escrowSnap Last known escrow balance (for snapshot diff) + * @param escrowSnap Snapshot of escrow balance at the last _setEscrowSnap call. + * Input to totalEscrowDeficit accounting, not a guarantee of the live balance — it can + * drift between reconciliations (e.g. after beforeCollection's JIT deposit) until the + * next _reconcileProviderEscrow resyncs it. Read the live balance via _fetchEscrowAccount + * when actual solvency matters. * @param agreements Set of agreement IDs for this pair (stored as bytes32 for EnumerableSet) */ struct CollectorProviderData { @@ -176,8 +180,9 @@ contract RecurringAgreementManager is /// @notice Total unfunded escrow: sum of max(0, sumMaxNextClaim[c][p] - escrowSnap[c][p]) uint256 totalEscrowDeficit; /// @notice The issuance allocator that mints GRT to this contract (20 bytes) - /// @dev Packed slot (28/32 bytes): issuanceAllocator (20) + ensuredIncomingDistributedToBlock (4) + - /// escrowBasis (1) + minOnDemandBasisThreshold (1) + minFullBasisMargin (1) + minThawFraction (1). + /// @dev Packed slot (29/32 bytes): issuanceAllocator (20) + ensuredIncomingDistributedToBlock (4) + + /// escrowBasis (1) + minOnDemandBasisThreshold (1) + minFullBasisMargin (1) + minThawFraction (1) + + /// minResidualEscrowFactor (1). /// All read together in _reconcileProviderEscrow / beforeCollection. IIssuanceAllocationDistribution issuanceAllocator; /// @notice Block number when _ensureIncomingDistributionToCurrentBlock last ran @@ -194,6 +199,13 @@ contract RecurringAgreementManager is /// per (collector, provider) pair is skipped as operationally insignificant. /// Governance-configured. uint8 minThawFraction; + /// @notice Minimum residual escrow factor: when a (collector, provider) pair has no agreements + /// and the escrow balance is below 2^value, tracking is dropped; the residual is not worth + /// the gas cost of further thaw/withdraw cycles. Funds remain in PaymentsEscrow but are no + /// longer actively managed by RAM. Higher values drop more aggressively: + /// 0 = drop only at zero balance (effectively never drop); 255 = always drop when no + /// agreements remain. Governance-configured. Default 50 ≈ 0.001 GRT. + uint8 minResidualEscrowFactor; /// @notice Optional oracle for checking payment eligibility of service providers (20/32 bytes in slot) IProviderEligibility providerEligibilityOracle; } @@ -231,6 +243,7 @@ contract RecurringAgreementManager is $.minOnDemandBasisThreshold = 128; $.minFullBasisMargin = 16; $.minThawFraction = 16; + $.minResidualEscrowFactor = 50; // 2^50 ≈ 10^15 ≈ 0.001 GRT } // -- ERC165 -- @@ -435,6 +448,16 @@ contract RecurringAgreementManager is emit MinThawFractionSet(oldFraction, fraction); } + /// @inheritdoc IRecurringEscrowManagement + function setMinResidualEscrowFactor(uint8 value) external onlyRole(OPERATOR_ROLE) { + RecurringAgreementManagerStorage storage $ = _getStorage(); + if ($.minResidualEscrowFactor == value) return; + + uint8 oldValue = $.minResidualEscrowFactor; + $.minResidualEscrowFactor = value; + emit MinResidualEscrowFactorSet(oldValue, value); + } + // -- IProviderEligibilityManagement -- /// @inheritdoc IProviderEligibilityManagement @@ -542,6 +565,11 @@ contract RecurringAgreementManager is return _getStorage().minThawFraction; } + /// @inheritdoc IRecurringAgreements + function getMinResidualEscrowFactor() external view returns (uint8) { + return _getStorage().minResidualEscrowFactor; + } + /// @inheritdoc IRecurringAgreements function getCollectorCount() external view returns (uint256) { return _getStorage().collectorSet.length(); @@ -683,9 +711,17 @@ contract RecurringAgreementManager is } /** - * @notice Reconcile escrow then remove (collector, provider) tracking if fully drained. - * @dev Calls {_reconcileProviderEscrow} to withdraw completed thaws, then removes the pair from - * tracking only when both agreement count and escrowSnap are zero. + * @notice Reconcile escrow then remove (collector, provider) tracking if below residual threshold. + * @dev For tracked pairs (in providerSet): runs {_reconcileProviderEscrow}, then drops tracking + * when no agreements remain and escrow balance is strictly below the residual threshold. + * For untracked pairs: performs a blind drain (withdraw matured thaw, thaw remainder) without + * re-creating tracking state. + * + * The residual threshold = 2^minResidualEscrowFactor. Below this, the residual is not worth + * the gas cost of further thaw/withdraw cycles, so tracking is dropped. Funds remain in + * PaymentsEscrow, just no longer actively managed by RAM. A subsequent {_offerAgreement} + * for the same pair will re-add tracking naturally. + * * Cascades to remove the collector when it has no remaining providers. * @param $ The storage reference * @param collector The collector contract address @@ -698,11 +734,22 @@ contract RecurringAgreementManager is address collector, address provider ) private returns (bool tracked) { + if (!$.collectors[collector].providerSet.contains(provider)) { + // Not tracked — blind drain without re-creating tracking state. + _drainUntracked(collector, provider); + return false; + } + _reconcileProviderEscrow($, collector, provider); CollectorProviderData storage cpd = $.collectors[collector].providers[provider]; - if (cpd.agreements.length() != 0 || cpd.escrowSnap != 0) tracked = true; - else if ($.collectors[collector].providerSet.remove(provider)) { + // Drop tracking when no agreements and escrow is below residual threshold. + // Funds remain in PaymentsEscrow; deficit contribution is already 0 (sumMaxNextClaim == 0). + // Read real balance (escrowSnap is already cleared when sumMaxNextClaim == 0). + tracked = + cpd.agreements.length() != 0 || + ((uint256(1) << $.minResidualEscrowFactor) <= _fetchEscrowAccount(collector, provider).balance); + if (!tracked && $.collectors[collector].providerSet.remove(provider)) { emit ProviderRemoved(collector, provider); if ($.collectors[collector].providerSet.length() == 0) { // Provider agreement count will already be zero at this point. @@ -712,6 +759,24 @@ contract RecurringAgreementManager is } } + /** + * @notice Blind drain for an untracked (collector, provider) escrow pair. + * @dev Withdraws matured thaw if any, then starts a new thaw for remaining balance. + * Does not read or write any RAM tracking state. Only acts when no thaw is active + * (after withdraw or if none was started), so thaw() is safe — no timer to reset. + * @param collector The collector contract address + * @param provider Service provider address + */ + function _drainUntracked(address collector, address provider) private { + IPaymentsEscrow.EscrowAccount memory account = _fetchEscrowAccount(collector, provider); + if (0 < account.tokensThawing && account.thawEndTimestamp < block.timestamp) { + PAYMENTS_ESCROW.withdraw(collector, provider); + account = _fetchEscrowAccount(collector, provider); + } + if (account.tokensThawing == 0 && 0 < account.balance) + PAYMENTS_ESCROW.thaw(collector, provider, account.balance); + } + /** * @notice The sole mutation point for agreement.maxNextClaim and all derived totals. * @dev ALL writes to agreement.maxNextClaim, sumMaxNextClaim, sumMaxNextClaimAll, and @@ -928,7 +993,8 @@ contract RecurringAgreementManager is address provider ) private { uint256 oldEscrow = cpd.escrowSnap; - uint256 newEscrow = _fetchEscrowAccount(collector, provider).balance; + // No need to track escrow when no claims remain (deficit is 0 regardless). + uint256 newEscrow = cpd.sumMaxNextClaim != 0 ? _fetchEscrowAccount(collector, provider).balance : 0; if (oldEscrow == newEscrow) return; uint256 oldDeficit = _providerEscrowDeficit(cpd); diff --git a/packages/issuance/test/unit/agreement-manager/cascadeCleanup.t.sol b/packages/issuance/test/unit/agreement-manager/cascadeCleanup.t.sol index eeffa61e1..b9d058c6c 100644 --- a/packages/issuance/test/unit/agreement-manager/cascadeCleanup.t.sol +++ b/packages/issuance/test/unit/agreement-manager/cascadeCleanup.t.sol @@ -193,6 +193,12 @@ contract RecurringAgreementManagerCascadeCleanupTest is RecurringAgreementManage assertEq(agreementManager.getCollectorCount(), 0); assertEq(agreementManager.getProviderCount(IAgreementCollector(address(recurringCollector))), 0); + // Storage fully released: escrowSnap cleared when sumMaxNextClaim reached 0 + assertEq( + agreementManager.getEscrowSnap(IAgreementCollector(address(recurringCollector)), indexer), + 0, + "escrowSnap should be cleared after pair drop" + ); } function test_Cascade_ReconcileLastProvider_CollectorCleanedUp_OtherCollectorRemains() public { @@ -271,6 +277,11 @@ contract RecurringAgreementManagerCascadeCleanupTest is RecurringAgreementManage vm.warp(block.timestamp + paymentsEscrow.THAWING_PERIOD() + 1); agreementManager.reconcileProvider(IAgreementCollector(address(recurringCollector)), indexer); assertEq(agreementManager.getCollectorCount(), 0); + assertEq( + agreementManager.getEscrowSnap(IAgreementCollector(address(recurringCollector)), indexer), + 0, + "escrowSnap clean before re-add" + ); // Re-add — sets repopulate (IRecurringCollector.RecurringCollectionAgreement memory rca2, ) = _makeRCAForCollector(recurringCollector, 2); diff --git a/packages/issuance/test/unit/agreement-manager/residualEscrow.t.sol b/packages/issuance/test/unit/agreement-manager/residualEscrow.t.sol new file mode 100644 index 000000000..c96003e67 --- /dev/null +++ b/packages/issuance/test/unit/agreement-manager/residualEscrow.t.sol @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import { IAgreementCollector } from "@graphprotocol/interfaces/contracts/horizon/IAgreementCollector.sol"; +import { IPaymentsEscrow } from "@graphprotocol/interfaces/contracts/horizon/IPaymentsEscrow.sol"; +import { IRecurringCollector } from "@graphprotocol/interfaces/contracts/horizon/IRecurringCollector.sol"; +import { IRecurringAgreementManagement } from "@graphprotocol/interfaces/contracts/issuance/agreement/IRecurringAgreementManagement.sol"; +import { IRecurringEscrowManagement } from "@graphprotocol/interfaces/contracts/issuance/agreement/IRecurringEscrowManagement.sol"; + +import { RecurringAgreementManagerSharedTest } from "./shared.t.sol"; + +/// @notice Tests for minResidualEscrowFactor — residual escrow threshold for pair cleanup. +contract RecurringAgreementManagerResidualEscrowTest is RecurringAgreementManagerSharedTest { + /* solhint-disable graph/func-name-mixedcase */ + + // -- Helpers -- + + /// @notice Create an agreement, cancel it, and advance past the thaw period so escrow is withdrawable. + function _createAndCancelAgreement() + private + returns (bytes16 agreementId, IRecurringCollector.RecurringCollectionAgreement memory rca) + { + (rca, ) = _makeRCAWithId(100 ether, 1 ether, 3600, uint64(block.timestamp + 365 days)); + agreementId = _offerAgreement(rca); + + _setAgreementCanceledBySP(agreementId, rca); + agreementManager.reconcileAgreement(IAgreementCollector(address(recurringCollector)), agreementId); + } + + /// @notice Inject dust directly into escrow (simulates external depositTo by attacker). + function _injectDust(uint256 amount) private { + (uint256 bal, uint256 thawing, uint256 thawEnd) = paymentsEscrow.escrowAccounts( + address(agreementManager), + address(recurringCollector), + indexer + ); + // Mint backing tokens to the escrow so withdraw can transfer them + token.mint(address(paymentsEscrow), amount); + paymentsEscrow.setAccount( + address(agreementManager), + address(recurringCollector), + indexer, + bal + amount, + thawing, + thawEnd + ); + } + + // -- Tests: residual threshold drops tracking -- + + function test_ResidualEscrow_DropsTrackingBelowThreshold() public { + // Default factor = 50, threshold = 2^50 ≈ 1.1e15 + _createAndCancelAgreement(); + + // Advance past thaw period so escrow can be withdrawn + vm.warp(block.timestamp + 1 days + 1); + + // reconcileProvider: withdraws full balance, dust is zero, pair is dropped + bool tracked = agreementManager.reconcileProvider(IAgreementCollector(address(recurringCollector)), indexer); + assertFalse(tracked, "pair should be dropped when escrow is zero"); + assertEq( + agreementManager.getProviderCount(IAgreementCollector(address(recurringCollector))), + 0, + "provider should be removed from set" + ); + assertEq(agreementManager.getCollectorCount(), 0, "collector should be removed from set"); + } + + function test_ResidualEscrow_KeepsTrackingAboveThreshold() public { + _createAndCancelAgreement(); + + // Inject balance well above threshold (2^50 ≈ 1.1e15) + vm.warp(block.timestamp + 1 days + 1); + _injectDust(1 ether); + + bool tracked = agreementManager.reconcileProvider(IAgreementCollector(address(recurringCollector)), indexer); + assertTrue(tracked, "pair should remain tracked when escrow exceeds threshold"); + } + + function test_ResidualEscrow_DustGriefingDropsTracking() public { + _createAndCancelAgreement(); + + // Advance past thaw, then inject 1 wei (simulates attacker depositTo) + vm.warp(block.timestamp + 1 days + 1); + _injectDust(1); + + // reconcileProvider: withdraws matured thaw, 1 wei remains, + // 1 wei < 2^50 threshold → pair is dropped + bool tracked = agreementManager.reconcileProvider(IAgreementCollector(address(recurringCollector)), indexer); + assertFalse(tracked, "dust should not prevent cleanup"); + } + + // -- Tests: blind drain for untracked pairs -- + + function test_ResidualEscrow_BlindDrainUntrackedPair() public { + _createAndCancelAgreement(); + + // Drop tracking first + vm.warp(block.timestamp + 1 days + 1); + agreementManager.reconcileProvider(IAgreementCollector(address(recurringCollector)), indexer); + assertEq(agreementManager.getProviderCount(IAgreementCollector(address(recurringCollector))), 0); + + // Inject dust into the now-untracked escrow + _injectDust(100); + + // reconcileProvider on untracked pair: blind drain starts thaw + bool tracked = agreementManager.reconcileProvider(IAgreementCollector(address(recurringCollector)), indexer); + assertFalse(tracked, "untracked pair should stay untracked"); + + // Escrow should now be thawing + (uint256 bal, uint256 thawing, ) = paymentsEscrow.escrowAccounts( + address(agreementManager), + address(recurringCollector), + indexer + ); + assertEq(thawing, bal, "full balance should be thawing"); + } + + function test_ResidualEscrow_BlindDrainWithdrawsMaturedThaw() public { + _createAndCancelAgreement(); + + // Drop tracking + vm.warp(block.timestamp + 1 days + 1); + agreementManager.reconcileProvider(IAgreementCollector(address(recurringCollector)), indexer); + + // Inject dust, start thaw via blind drain + _injectDust(100); + agreementManager.reconcileProvider(IAgreementCollector(address(recurringCollector)), indexer); + + // Read the thaw end timestamp and advance past it + (, , uint256 thawEnd) = paymentsEscrow.escrowAccounts( + address(agreementManager), + address(recurringCollector), + indexer + ); + vm.warp(thawEnd + 1); + + uint256 balBefore = token.balanceOf(address(agreementManager)); + agreementManager.reconcileProvider(IAgreementCollector(address(recurringCollector)), indexer); + uint256 balAfter = token.balanceOf(address(agreementManager)); + + assertEq(balAfter - balBefore, 100, "dust should be withdrawn to agreement manager"); + } + + function test_ResidualEscrow_BlindDrainNoopMidThaw() public { + _createAndCancelAgreement(); + + // Drop tracking + vm.warp(block.timestamp + 1 days + 1); + agreementManager.reconcileProvider(IAgreementCollector(address(recurringCollector)), indexer); + + // Inject dust, start thaw + _injectDust(100); + agreementManager.reconcileProvider(IAgreementCollector(address(recurringCollector)), indexer); + + // Inject more dust mid-thaw — blind drain should NOT reset the timer + _injectDust(50); + + (, , uint256 thawEndBefore) = paymentsEscrow.escrowAccounts( + address(agreementManager), + address(recurringCollector), + indexer + ); + + agreementManager.reconcileProvider(IAgreementCollector(address(recurringCollector)), indexer); + + (, uint256 thawingAfter, uint256 thawEndAfter) = paymentsEscrow.escrowAccounts( + address(agreementManager), + address(recurringCollector), + indexer + ); + + // Timer should not have reset (evenIfTimerReset=false) + assertEq(thawEndAfter, thawEndBefore, "thaw timer should not reset on blind drain mid-thaw"); + // Only the original 100 should be thawing, not 150 + assertEq(thawingAfter, 100, "thaw amount should not increase mid-thaw"); + } + + // -- Tests: re-entry after drop restores tracking -- + + function test_ResidualEscrow_ReentryRestoresTracking() public { + _createAndCancelAgreement(); + + // Drop tracking + vm.warp(block.timestamp + 1 days + 1); + agreementManager.reconcileProvider(IAgreementCollector(address(recurringCollector)), indexer); + assertEq(agreementManager.getCollectorCount(), 0, "collector should be removed"); + + // New agreement for the same (collector, provider) pair + IRecurringCollector.RecurringCollectionAgreement memory rca2 = _makeRCA( + 50 ether, + 0.5 ether, + 60, + 3600, + uint64(block.timestamp + 365 days) + ); + rca2.nonce = 2; + _offerAgreement(rca2); + + // Tracking should be restored + assertEq( + agreementManager.getProviderCount(IAgreementCollector(address(recurringCollector))), + 1, + "provider should be re-tracked" + ); + assertEq(agreementManager.getCollectorCount(), 1, "collector should be re-tracked"); + } + + function test_ResidualEscrow_ReentryWithStaleSnapCorrects() public { + _createAndCancelAgreement(); + + // Inject extra balance, then drop tracking — snap records the inflated balance + _injectDust(500); + vm.warp(block.timestamp + 1 days + 1); + agreementManager.reconcileProvider(IAgreementCollector(address(recurringCollector)), indexer); + + // Escrow still has some balance (the dust that was below threshold or leftover) + // Now create new agreement — snap should be corrected from real balance + IRecurringCollector.RecurringCollectionAgreement memory rca2 = _makeRCA( + 50 ether, + 0.5 ether, + 60, + 3600, + uint64(block.timestamp + 365 days) + ); + rca2.nonce = 2; + _offerAgreement(rca2); + + // The system should work normally — no stale snap causing issues + // Verify escrow is funded correctly for the new agreement + (uint256 bal, , ) = paymentsEscrow.escrowAccounts( + address(agreementManager), + address(recurringCollector), + indexer + ); + uint256 expectedMaxClaim = 0.5 ether * 3600 + 50 ether; + assertEq(bal, expectedMaxClaim, "escrow should be funded for new agreement (snap corrected)"); + } + + // -- Tests: setter -- + + function test_ResidualEscrow_SetFactor() public { + assertEq(agreementManager.getMinResidualEscrowFactor(), 50, "default should be 50"); + + vm.prank(operator); + agreementManager.setMinResidualEscrowFactor(60); + assertEq(agreementManager.getMinResidualEscrowFactor(), 60); + } + + function test_ResidualEscrow_SetFactor_SameValueNoop() public { + vm.prank(operator); + // Should not emit event + vm.recordLogs(); + agreementManager.setMinResidualEscrowFactor(50); + assertEq(vm.getRecordedLogs().length, 0, "no event on same value"); + } + + function test_ResidualEscrow_SetFactor_EmitsEvent() public { + vm.expectEmit(address(agreementManager)); + emit IRecurringEscrowManagement.MinResidualEscrowFactorSet(50, 100); + + vm.prank(operator); + agreementManager.setMinResidualEscrowFactor(100); + } + + function test_ResidualEscrow_SetFactor_ZeroDisables() public { + _createAndCancelAgreement(); + + vm.prank(operator); + agreementManager.setMinResidualEscrowFactor(0); + + // With factor=0, threshold = 2^0 = 1, only drops at zero balance + // Inject 1 wei — should keep tracking + vm.warp(block.timestamp + 1 days + 1); + _injectDust(1); + + bool tracked = agreementManager.reconcileProvider(IAgreementCollector(address(recurringCollector)), indexer); + assertTrue(tracked, "factor=0 means threshold=1, 1 wei should keep tracking"); + } +} From a76efa8fcc0ab77b46351fad94a542ae429fb6ff Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Sun, 19 Apr 2026 19:03:14 +0000 Subject: [PATCH 06/31] docs: add responses to TRST-L-6, TRST-R-7 (both won't fix) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TRST-L-6: planted-offer-matching-active-terms cleanup bypass — rejected because cross-type EIP-712 collisions are computationally infeasible and same-type 'collisions' require the payer to reproduce their own terms, which is not an attack. TRST-R-7: eagerly delete consumed offers — rejected because offer data (metadata, nonce, deadline) is intentionally kept accessible via getAgreementOfferAt() until obsolete. --- packages/issuance/audits/PR1301/TRST-L-6.md | 4 ++++ packages/issuance/audits/PR1301/TRST-R-7.md | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/packages/issuance/audits/PR1301/TRST-L-6.md b/packages/issuance/audits/PR1301/TRST-L-6.md index c0792c908..50b3bf72f 100644 --- a/packages/issuance/audits/PR1301/TRST-L-6.md +++ b/packages/issuance/audits/PR1301/TRST-L-6.md @@ -22,3 +22,7 @@ Delete both `rcaOffers[agreementId]` and `rcauOffers[agreementId]` unconditional TBD --- + +The described attack requires planting an RCA offer whose EIP-712 hash collides with the active `activeTermsHash`. Because `_hashRCA` and `_hashRCAU` use distinct type hashes (`EIP712_RCA_TYPEHASH` vs `EIP712_RCAU_TYPEHASH`), cross-type collisions require a keccak256 preimage collision? Same-type collisions require the payer to reproduce the exact RCA terms, which is not an attack (the payer authored those terms). + +(Cleanup handling will be improved in combination with the response to TRST-L-11.) diff --git a/packages/issuance/audits/PR1301/TRST-R-7.md b/packages/issuance/audits/PR1301/TRST-R-7.md index 903eaaea7..65f7ae98c 100644 --- a/packages/issuance/audits/PR1301/TRST-R-7.md +++ b/packages/issuance/audits/PR1301/TRST-R-7.md @@ -5,3 +5,9 @@ ## Description After `accept()` or `update()` consumes a stored offer, the corresponding entry in `rcaOffers` or `rcauOffers` becomes stale. Currently only `_validateAndStoreUpdate()` cleans up the previously active offer by looking up the old `activeTermsHash`; the offer whose terms were just accepted is not deleted. This is a storage hygiene concern: stale offer entries remain in storage indefinitely until explicitly replaced or matched by a future update. Consider deleting the consumed offer entry inside `accept()` and `update()` after it has been applied. + +--- + +Keeping consumed offers in storage is by design — offer data (including metadata, nonce, deadline) remains accessible on-chain via `getAgreementOfferAt()` until the terms are obsolete. Stale entries are cleaned up by `_validateAndStoreUpdate()` on the next update, overwritten by a new `offer()`, or removed by `cancel()`. Eagerly deleting on consumption would lose data that callers may still want to inspect. + +(Cleanup handling will be improved in combination with the response to TRST-L-11.) From 4651d255e356c19f115b461d592ccb110ff79db7 Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Mon, 20 Apr 2026 13:21:01 +0000 Subject: [PATCH 07/31] docs(audit): acknowledge TRST-R-3 cancelAgreement defensive check The RAM's cancelAgreement is now a pass-through to collector.cancel(), which requires agreement.state == AgreementState.Accepted. The defensive guard the recommendation asks for already lives in the single authoritative location for agreement state; no further change required. --- packages/issuance/audits/PR1301/TRST-R-3.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/issuance/audits/PR1301/TRST-R-3.md b/packages/issuance/audits/PR1301/TRST-R-3.md index d3fa90130..0e012a072 100644 --- a/packages/issuance/audits/PR1301/TRST-R-3.md +++ b/packages/issuance/audits/PR1301/TRST-R-3.md @@ -5,3 +5,7 @@ ## Description In the RAM's `cancelAgreement()` function, the agreement state is required to not be not accepted. However, the logic could be more specific and require the agreement to be Accepted - rejecting previously cancelled agreements. There is no impact because corresponding checks in the RecurringCollector would deny such cancels, but it remains as a best practice. + +--- + +Fixed. The RAM's `cancelAgreement()` was refactored into a pass-through to `collector.cancel()`, which requires `agreement.state == AgreementState.Accepted` before proceeding. The defensive guard now lives in the single authoritative location for agreement state. From eae04c9c8f2b30d6e03349605ef8ff89a49c3cf8 Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Mon, 20 Apr 2026 13:21:01 +0000 Subject: [PATCH 08/31] fix(collector): remove dead oldHash guard (TRST-R-6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _validateAndStoreUpdate's `if (oldHash != bytes32(0))` branch was unreachable — every Accepted agreement has a non-zero activeTermsHash written during accept() or a prior update(). Dropped the guard; the offer cleanup is now unconditional with an inline comment noting the invariant. --- .../contracts/payments/collectors/RecurringCollector.sol | 7 +++---- packages/issuance/audits/PR1301/TRST-R-6.md | 4 ++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol index 2cfb38767..3630a1d88 100644 --- a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol +++ b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol @@ -1040,11 +1040,10 @@ contract RecurringCollector is // Reverts on overflow — rejecting excessive terms that could prevent collection _rcau.maxOngoingTokensPerSecond * _rcau.maxSecondsPerCollection * 1024; - // Clean up stored replaced offer + // Clean up stored replaced offer — oldHash is always non-zero for accepted agreements bytes32 oldHash = _agreement.activeTermsHash; - if (oldHash != bytes32(0)) - if ($.rcaOffers[_rcau.agreementId].offerHash == oldHash) delete $.rcaOffers[_rcau.agreementId]; - else if ($.rcauOffers[_rcau.agreementId].offerHash == oldHash) delete $.rcauOffers[_rcau.agreementId]; + if ($.rcaOffers[_rcau.agreementId].offerHash == oldHash) delete $.rcaOffers[_rcau.agreementId]; + else if ($.rcauOffers[_rcau.agreementId].offerHash == oldHash) delete $.rcauOffers[_rcau.agreementId]; // update the agreement _agreement.endsAt = _rcau.endsAt; diff --git a/packages/issuance/audits/PR1301/TRST-R-6.md b/packages/issuance/audits/PR1301/TRST-R-6.md index 9fa653c5f..76460062d 100644 --- a/packages/issuance/audits/PR1301/TRST-R-6.md +++ b/packages/issuance/audits/PR1301/TRST-R-6.md @@ -5,3 +5,7 @@ ## Description In `_validateAndStoreUpdate()` (line 855), the guard `if (oldHash != bytes32(0))` is unreachable as a false branch. Only agreements in the Accepted state may be updated, and every accepted agreement has a non-zero `activeTermsHash` written during `accept()` or a prior `update()`. The guard can be removed or converted into an invariant comment documenting this assumption. + +--- + +Fixed. Removed the dead `if (oldHash != bytes32(0))` guard. The offer cleanup is now unconditional with an inline comment noting the invariant. From 94d2695f4858966aa7254f244052ccc2ef6b4a38 Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Sun, 19 Apr 2026 16:07:23 +0000 Subject: [PATCH 09/31] fix(collector): non-zero offer types, reserve OFFER_TYPE_NONE=0 sentinel (TRST-R-5) getAgreementOfferAt callers could not distinguish a stored OFFER_TYPE_NEW (value 0) from the zero default returned when no offer exists. Make the offer type flags non-zero (NEW=1, UPDATE=2), reserve 0 as the named OFFER_TYPE_NONE sentinel, and use it at the no-offer return site. --- .../collectors/RecurringCollector.sol | 1 + .../offerStorageLifecycle.t.sol | 355 ++++++++++++++++++ .../contracts/horizon/IAgreementCollector.sol | 11 +- packages/issuance/audits/PR1301/TRST-R-5.md | 4 + 4 files changed, 367 insertions(+), 4 deletions(-) create mode 100644 packages/horizon/test/unit/payments/recurring-collector/offerStorageLifecycle.t.sol diff --git a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol index 3630a1d88..d8a8c63c1 100644 --- a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol +++ b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol @@ -15,6 +15,7 @@ import { IPaymentsCollector } from "@graphprotocol/interfaces/contracts/horizon/ import { IAgreementOwner } from "@graphprotocol/interfaces/contracts/horizon/IAgreementOwner.sol"; import { IAgreementCollector, + OFFER_TYPE_NONE, OFFER_TYPE_NEW, OFFER_TYPE_UPDATE, ACCEPTED, diff --git a/packages/horizon/test/unit/payments/recurring-collector/offerStorageLifecycle.t.sol b/packages/horizon/test/unit/payments/recurring-collector/offerStorageLifecycle.t.sol new file mode 100644 index 000000000..0aece90ae --- /dev/null +++ b/packages/horizon/test/unit/payments/recurring-collector/offerStorageLifecycle.t.sol @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import { Vm } from "forge-std/Vm.sol"; + +import { IRecurringCollector } from "@graphprotocol/interfaces/contracts/horizon/IRecurringCollector.sol"; +import { + IAgreementCollector, + OFFER_TYPE_NONE, + OFFER_TYPE_NEW, + OFFER_TYPE_UPDATE, + SCOPE_PENDING, + VERSION_CURRENT, + VERSION_NEXT +} from "@graphprotocol/interfaces/contracts/horizon/IAgreementCollector.sol"; + +import { RecurringCollectorSharedTest } from "./shared.t.sol"; +import { MockAgreementOwner } from "./MockAgreementOwner.t.sol"; + +/// @notice Targeted coverage for the hash-keyed offer storage refactor. +contract RecurringCollectorOfferStorageLifecycleTest is RecurringCollectorSharedTest { + /* solhint-disable graph/func-name-mixedcase */ + + function _makeRca(address payer) internal returns (IRecurringCollector.RecurringCollectionAgreement memory) { + return + _recurringCollectorHelper.sensibleRCA( + IRecurringCollector.RecurringCollectionAgreement({ + deadline: uint64(block.timestamp + 1 hours), + endsAt: uint64(block.timestamp + 365 days), + payer: payer, + dataService: makeAddr("ds"), + serviceProvider: makeAddr("sp"), + maxInitialTokens: 100 ether, + maxOngoingTokensPerSecond: 1 ether, + minSecondsPerCollection: 600, + maxSecondsPerCollection: 3600, + conditions: 0, + nonce: 1, + metadata: "" + }) + ); + } + + function _makeRcau( + bytes16 agreementId, + IRecurringCollector.RecurringCollectionAgreement memory rca, + uint32 nonce + ) internal view returns (IRecurringCollector.RecurringCollectionAgreementUpdate memory) { + return + IRecurringCollector.RecurringCollectionAgreementUpdate({ + agreementId: agreementId, + deadline: uint64(block.timestamp + 1 hours), + endsAt: rca.endsAt + 30 days, + maxInitialTokens: rca.maxInitialTokens, + maxOngoingTokensPerSecond: rca.maxOngoingTokensPerSecond * 2, + minSecondsPerCollection: rca.minSecondsPerCollection, + maxSecondsPerCollection: rca.maxSecondsPerCollection, + conditions: 0, + nonce: nonce, + metadata: "" + }); + } + + // ────────────────────────────────────────────────────────────────────── + // Hash-keyed offer storage lifecycle + // ────────────────────────────────────────────────────────────────────── + + /// @notice offer(RCA) creates a storage entry at the EIP-712 hash and emits OfferStored. + function test_OfferNew_StoresEntryAtHash_EmitsEvent() public { + MockAgreementOwner approver = new MockAgreementOwner(); + IRecurringCollector.RecurringCollectionAgreement memory rca = _makeRca(address(approver)); + bytes32 rcaHash = _recurringCollector.hashRCA(rca); + bytes16 agreementId = _recurringCollector.generateAgreementId( + rca.payer, + rca.dataService, + rca.serviceProvider, + rca.deadline, + rca.nonce + ); + + vm.expectEmit(address(_recurringCollector)); + emit IRecurringCollector.OfferStored(agreementId, rca.payer, OFFER_TYPE_NEW, rcaHash); + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_NEW, abi.encode(rca), 0); + + (uint8 offerType, bytes memory offerData) = _recurringCollector.getAgreementOfferAt( + agreementId, + VERSION_CURRENT + ); + assertEq(offerType, OFFER_TYPE_NEW, "stored entry at rcaHash"); + assertTrue(offerData.length > 0, "stored data non-empty"); + + IRecurringCollector.AgreementData memory agreement = _recurringCollector.getAgreement(agreementId); + assertEq(agreement.activeTermsHash, rcaHash, "agreement.activeTermsHash points at offer hash"); + assertEq(agreement.pendingTermsHash, bytes32(0), "no pending before update"); + } + + /// @notice Re-offering the identical RCA is idempotent — no second OfferStored event, storage unchanged. + function test_OfferNew_Idempotent_WhenResubmittedSameHash() public { + MockAgreementOwner approver = new MockAgreementOwner(); + IRecurringCollector.RecurringCollectionAgreement memory rca = _makeRca(address(approver)); + + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_NEW, abi.encode(rca), 0); + + // Second call with the same RCA must not emit OfferStored again + vm.recordLogs(); + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_NEW, abi.encode(rca), 0); + Vm.Log[] memory logs = vm.getRecordedLogs(); + bytes32 offerStoredSig = keccak256("OfferStored(bytes16,address,uint8,bytes32)"); + for (uint256 i = 0; i < logs.length; i++) { + if (logs[i].topics.length > 0) { + assertFalse(logs[i].topics[0] == offerStoredSig, "no duplicate OfferStored on re-offer"); + } + } + } + + /// @notice Accepting a stored offer preserves the offer entry — getAgreementOfferAt still returns it. + function test_OfferNew_EntryPersistsAcrossAccept() public { + MockAgreementOwner approver = new MockAgreementOwner(); + IRecurringCollector.RecurringCollectionAgreement memory rca = _makeRca(address(approver)); + _setupValidProvision(rca.serviceProvider, rca.dataService); + + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_NEW, abi.encode(rca), 0); + vm.prank(rca.dataService); + bytes16 agreementId = _recurringCollector.accept(rca, ""); + + (uint8 offerType, bytes memory offerData) = _recurringCollector.getAgreementOfferAt( + agreementId, + VERSION_CURRENT + ); + assertEq(offerType, OFFER_TYPE_NEW, "accept does not delete the RCA offer entry"); + assertTrue(offerData.length > 0, "accept preserves stored data"); + } + + /// @notice A successful update deletes the prior active offer from storage; the new RCAU terms + /// become VERSION_CURRENT (OFFER_TYPE_UPDATE) and the pending slot clears. + function test_Update_DeletesPriorActiveOffer_PromotesRcauToCurrent() public { + MockAgreementOwner approver = new MockAgreementOwner(); + IRecurringCollector.RecurringCollectionAgreement memory rca = _makeRca(address(approver)); + _setupValidProvision(rca.serviceProvider, rca.dataService); + + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_NEW, abi.encode(rca), 0); + vm.prank(rca.dataService); + bytes16 agreementId = _recurringCollector.accept(rca, ""); + bytes32 rcaHash = _recurringCollector.hashRCA(rca); + + IRecurringCollector.RecurringCollectionAgreementUpdate memory rcau = _makeRcau(agreementId, rca, 1); + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_UPDATE, abi.encode(rcau), 0); + bytes32 rcauHash = _recurringCollector.hashRCAU(rcau); + + vm.prank(rca.dataService); + _recurringCollector.update(rcau, ""); + + // Prior active (RCA) offer deleted from storage — since activeTermsHash now points at rcauHash, + // a fresh agreementId derived with mismatched hash should return empty at the rcaHash slot. + // We assert via getAgreementDetails: rcaHash is no longer a current version. + IRecurringCollector.AgreementData memory agreement = _recurringCollector.getAgreement(agreementId); + assertEq(agreement.activeTermsHash, rcauHash, "activeTermsHash = rcauHash after update"); + assertEq(agreement.pendingTermsHash, bytes32(0), "pendingTermsHash cleared after update"); + + (uint8 currentType, ) = _recurringCollector.getAgreementOfferAt(agreementId, VERSION_CURRENT); + assertEq(currentType, OFFER_TYPE_UPDATE, "current offer type now OFFER_TYPE_UPDATE"); + + (uint8 nextType, bytes memory nextData) = _recurringCollector.getAgreementOfferAt(agreementId, VERSION_NEXT); + assertEq(nextType, OFFER_TYPE_NONE, "no pending offer after update"); + assertEq(nextData.length, 0, "pending data empty after update"); + + // Old RCA hash is no longer referenced; since getAgreementOfferAt only resolves via version + // indices, confirm indirectly that no version maps to rcaHash. + bytes32 currentHash = _recurringCollector.getAgreementDetails(agreementId, VERSION_CURRENT).versionHash; + assertTrue(currentHash != rcaHash, "no version maps to old rcaHash"); + } + + /// @notice Offering a different pending update replaces the prior pending RCAU — the replaced + /// entry is deleted from storage. + function test_OfferUpdate_ReplacesPriorPending_DeletesReplaced() public { + MockAgreementOwner approver = new MockAgreementOwner(); + IRecurringCollector.RecurringCollectionAgreement memory rca = _makeRca(address(approver)); + _setupValidProvision(rca.serviceProvider, rca.dataService); + + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_NEW, abi.encode(rca), 0); + vm.prank(rca.dataService); + bytes16 agreementId = _recurringCollector.accept(rca, ""); + + IRecurringCollector.RecurringCollectionAgreementUpdate memory rcauA = _makeRcau(agreementId, rca, 1); + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_UPDATE, abi.encode(rcauA), 0); + bytes32 rcauAHash = _recurringCollector.hashRCAU(rcauA); + + // Second update with different terms (different maxInitialTokens) replaces the pending RCAU + IRecurringCollector.RecurringCollectionAgreementUpdate memory rcauB = rcauA; + rcauB.maxInitialTokens = rcauA.maxInitialTokens + 1; + bytes32 rcauBHash = _recurringCollector.hashRCAU(rcauB); + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_UPDATE, abi.encode(rcauB), 0); + + IRecurringCollector.AgreementData memory agreement = _recurringCollector.getAgreement(agreementId); + assertEq(agreement.pendingTermsHash, rcauBHash, "pending now points to rcauB"); + + // Replaced rcauA entry no longer referenced by any version — VERSION_NEXT is now rcauB. + bytes32 pendingHash = _recurringCollector.getAgreementDetails(agreementId, VERSION_NEXT).versionHash; + assertEq(pendingHash, rcauBHash, "VERSION_NEXT resolves to rcauB"); + assertTrue(pendingHash != rcauAHash, "old rcauA no longer reachable via version index"); + } + + // ────────────────────────────────────────────────────────────────────── + // Pre-acceptance cancel cascades deletion of any pending RCAU + // ────────────────────────────────────────────────────────────────────── + + /// @notice Pre-acceptance cancel of the RCA under SCOPE_PENDING deletes BOTH the RCA offer + /// and any pending RCAU offer. After cascade, both slots are empty. + function test_CancelPreAcceptanceRca_CascadesDeleteRcau() public { + MockAgreementOwner approver = new MockAgreementOwner(); + IRecurringCollector.RecurringCollectionAgreement memory rca = _makeRca(address(approver)); + bytes32 rcaHash = _recurringCollector.hashRCA(rca); + + vm.prank(address(approver)); + IAgreementCollector.AgreementDetails memory rcaDetails = _recurringCollector.offer( + OFFER_TYPE_NEW, + abi.encode(rca), + 0 + ); + bytes16 agreementId = rcaDetails.agreementId; + + IRecurringCollector.RecurringCollectionAgreementUpdate memory rcau = _makeRcau(agreementId, rca, 1); + bytes32 rcauHash = _recurringCollector.hashRCAU(rcau); + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_UPDATE, abi.encode(rcau), 0); + + // Sanity: both slots populated before the cancel + (uint8 preCurrentType, ) = _recurringCollector.getAgreementOfferAt(agreementId, VERSION_CURRENT); + (uint8 preNextType, ) = _recurringCollector.getAgreementOfferAt(agreementId, VERSION_NEXT); + assertEq(preCurrentType, OFFER_TYPE_NEW, "RCA stored before cancel"); + assertEq(preNextType, OFFER_TYPE_UPDATE, "RCAU stored before cancel"); + + // Cancel the pre-acceptance RCA — one OfferCancelled event, both slots cleared + vm.expectEmit(address(_recurringCollector)); + emit IRecurringCollector.OfferCancelled(address(approver), agreementId, rcaHash); + vm.prank(address(approver)); + _recurringCollector.cancel(agreementId, rcaHash, SCOPE_PENDING); + + IRecurringCollector.AgreementData memory agreement = _recurringCollector.getAgreement(agreementId); + assertEq(agreement.activeTermsHash, bytes32(0), "activeTermsHash cleared"); + assertEq(agreement.pendingTermsHash, bytes32(0), "pendingTermsHash cascade-cleared"); + + (uint8 currentType, bytes memory currentData) = _recurringCollector.getAgreementOfferAt( + agreementId, + VERSION_CURRENT + ); + assertEq(currentType, OFFER_TYPE_NONE, "RCA offer deleted"); + assertEq(currentData.length, 0, "RCA data empty"); + + (uint8 nextType, bytes memory nextData) = _recurringCollector.getAgreementOfferAt(agreementId, VERSION_NEXT); + assertEq(nextType, OFFER_TYPE_NONE, "RCAU offer cascade-deleted"); + assertEq(nextData.length, 0, "RCAU data empty"); + + // The original rcauHash stored-offer entry is no longer referenced. No version hash + // resolves to it — confirmed above — so the cleanup is complete for view purposes. + rcauHash; // silence unused warning; kept for clarity in the narrative + } + + /// @notice After a pre-acceptance cascade delete, a follow-up cancel targeting the orphan RCAU + /// hash must NOT revert: _requirePayerIfExists short-circuits because agreement.payer was + /// zeroed when activeTermsHash was cleared — but the agreement struct still exists. The cancel + /// is therefore a no-op targeting already-empty state. + function test_CancelPreAcceptanceRca_SubsequentRcauCancel_DoesNotRevert() public { + MockAgreementOwner approver = new MockAgreementOwner(); + IRecurringCollector.RecurringCollectionAgreement memory rca = _makeRca(address(approver)); + bytes32 rcaHash = _recurringCollector.hashRCA(rca); + + vm.prank(address(approver)); + IAgreementCollector.AgreementDetails memory rcaDetails = _recurringCollector.offer( + OFFER_TYPE_NEW, + abi.encode(rca), + 0 + ); + bytes16 agreementId = rcaDetails.agreementId; + + IRecurringCollector.RecurringCollectionAgreementUpdate memory rcau = _makeRcau(agreementId, rca, 1); + bytes32 rcauHash = _recurringCollector.hashRCAU(rcau); + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_UPDATE, abi.encode(rcau), 0); + + // Cancel the RCA — cascades the RCAU + vm.prank(address(approver)); + _recurringCollector.cancel(agreementId, rcaHash, SCOPE_PENDING); + + // The approver can still cancel(rcauHash) without reverting — the payer slot on the + // agreement is still set (clearing is by *termsHash*, not payer field), so the call + // enters the pending-hash branch, observes pendingTermsHash == 0, and exits silently. + vm.prank(address(approver)); + _recurringCollector.cancel(agreementId, rcauHash, SCOPE_PENDING); + } + + /// @notice Pre-acceptance cancel with no pending RCAU still deletes the RCA offer and + /// emits a single OfferCancelled. + function test_CancelPreAcceptanceRca_NoPending_OnlyDeletesRca() public { + MockAgreementOwner approver = new MockAgreementOwner(); + IRecurringCollector.RecurringCollectionAgreement memory rca = _makeRca(address(approver)); + bytes32 rcaHash = _recurringCollector.hashRCA(rca); + + vm.prank(address(approver)); + IAgreementCollector.AgreementDetails memory details = _recurringCollector.offer( + OFFER_TYPE_NEW, + abi.encode(rca), + 0 + ); + bytes16 agreementId = details.agreementId; + + vm.expectEmit(address(_recurringCollector)); + emit IRecurringCollector.OfferCancelled(address(approver), agreementId, rcaHash); + vm.prank(address(approver)); + _recurringCollector.cancel(agreementId, rcaHash, SCOPE_PENDING); + + (uint8 currentType, ) = _recurringCollector.getAgreementOfferAt(agreementId, VERSION_CURRENT); + assertEq(currentType, OFFER_TYPE_NONE, "RCA offer deleted"); + assertEq(_recurringCollector.getAgreement(agreementId).activeTermsHash, bytes32(0), "activeTermsHash cleared"); + } + + // ────────────────────────────────────────────────────────────────────── + // OFFER_TYPE_NONE sentinel + // ────────────────────────────────────────────────────────────────────── + + /// @notice The offer-type sentinel values: OFFER_TYPE_NONE must be 0 so callers can distinguish + /// "no offer stored" (default mapping value) from OFFER_TYPE_NEW / OFFER_TYPE_UPDATE. + function test_OfferTypeConstants_NoneIsZero_OthersNonZero() public pure { + assertEq(OFFER_TYPE_NONE, uint8(0), "OFFER_TYPE_NONE must be 0"); + assertTrue(OFFER_TYPE_NEW != OFFER_TYPE_NONE, "OFFER_TYPE_NEW distinct from NONE"); + assertTrue(OFFER_TYPE_UPDATE != OFFER_TYPE_NONE, "OFFER_TYPE_UPDATE distinct from NONE"); + assertTrue(OFFER_TYPE_NEW != OFFER_TYPE_UPDATE, "NEW and UPDATE distinct"); + } + + /// @notice offer() rejects OFFER_TYPE_NONE as an offer type — the sentinel cannot be used to + /// create a stored offer, so getAgreementOfferAt's OFFER_TYPE_NONE return unambiguously means + /// "no offer stored". + function test_Offer_Revert_WhenOfferTypeIsNone() public { + MockAgreementOwner approver = new MockAgreementOwner(); + IRecurringCollector.RecurringCollectionAgreement memory rca = _makeRca(address(approver)); + bytes memory data = abi.encode(rca); + + vm.expectRevert( + abi.encodeWithSelector(IRecurringCollector.RecurringCollectorInvalidCollectData.selector, data) + ); + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_NONE, data, 0); + } + + /* solhint-enable graph/func-name-mixedcase */ +} diff --git a/packages/interfaces/contracts/horizon/IAgreementCollector.sol b/packages/interfaces/contracts/horizon/IAgreementCollector.sol index ee8bad086..ee3a5bd80 100644 --- a/packages/interfaces/contracts/horizon/IAgreementCollector.sol +++ b/packages/interfaces/contracts/horizon/IAgreementCollector.sol @@ -44,10 +44,13 @@ uint16 constant AUTO_UPDATED = 512; // -- Offer type constants -- +/// @dev No stored offer — sentinel returned by {IAgreementCollector.getAgreementOfferAt} +/// when the requested version has no offer data. +uint8 constant OFFER_TYPE_NONE = 0; /// @dev Create a new agreement -uint8 constant OFFER_TYPE_NEW = 0; +uint8 constant OFFER_TYPE_NEW = 1; /// @dev Update an existing agreement -uint8 constant OFFER_TYPE_UPDATE = 1; +uint8 constant OFFER_TYPE_UPDATE = 2; // -- Cancel scope constants -- @@ -154,8 +157,8 @@ interface IAgreementCollector is IPaymentsCollector { * original struct. Callers can decode and hash to verify the stored version hash. * @param agreementId The ID of the agreement * @param index The zero-based version index - * @return offerType OFFER_TYPE_NEW (0) or OFFER_TYPE_UPDATE (1) - * @return offerData ABI-encoded original offer struct + * @return offerType OFFER_TYPE_NEW, OFFER_TYPE_UPDATE, or OFFER_TYPE_NONE when no offer is stored + * @return offerData ABI-encoded original offer struct, or empty when offerType is OFFER_TYPE_NONE */ function getAgreementOfferAt( bytes16 agreementId, diff --git a/packages/issuance/audits/PR1301/TRST-R-5.md b/packages/issuance/audits/PR1301/TRST-R-5.md index f3d5ac72e..0db3ff607 100644 --- a/packages/issuance/audits/PR1301/TRST-R-5.md +++ b/packages/issuance/audits/PR1301/TRST-R-5.md @@ -5,3 +5,7 @@ ## Description `getAgreementOfferAt()` returns `(uint8 offerType, bytes memory offerData)`. The offer type constant `OFFER_TYPE_NEW` is defined as 0, which is also the default Solidity return value when no stored offer exists for the given `agreementId` and index. A caller receiving `offerType == 0` cannot distinguish between a stored new-type offer existing and no offer existing. Consider redefining offer type constants with 1-indexed values, or adding an explicit `bool found` return parameter. + +--- + +Using non-zero offer type constants as suggested: `OFFER_TYPE_NEW = 1`, `OFFER_TYPE_UPDATE = 2`. The zero value is declared explicitly as `OFFER_TYPE_NONE` so the "no stored offer" sentinel is part of the interface rather than a NatSpec-only convention. From 9fa57f84586e8de09e6ec225a135610247f2d61a Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Sun, 19 Apr 2026 18:46:34 +0000 Subject: [PATCH 10/31] refactor(interfaces): drop unused state and offer-option flags, tighten flag NatSpec (TRST-R-11) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove AUTO_UPDATE, AUTO_UPDATED, and BY_DATA_SERVICE from IAgreementCollector: none had an implementation path or in-tree consumer (RecurringCollector's cancel vocabulary is Payer / ServiceProvider only; there is no auto-update feature). Remaining flags' NatSpec tightened to describe the semantics they now carry after R-12. Also drop WITH_NOTICE and IF_NOT_ACCEPTED: declared on the unsigned offer path but never referenced — offer() ignores its options parameter. Parameter NatSpec now describes the bitmask as reserved for implementation-specific use. --- .../contracts/horizon/IAgreementCollector.sol | 35 +++++-------------- packages/issuance/audits/PR1301/TRST-R-11.md | 6 ++++ 2 files changed, 14 insertions(+), 27 deletions(-) diff --git a/packages/interfaces/contracts/horizon/IAgreementCollector.sol b/packages/interfaces/contracts/horizon/IAgreementCollector.sol index ee3a5bd80..f26622adc 100644 --- a/packages/interfaces/contracts/horizon/IAgreementCollector.sol +++ b/packages/interfaces/contracts/horizon/IAgreementCollector.sol @@ -4,26 +4,23 @@ pragma solidity ^0.8.22; import { IPaymentsCollector } from "./IPaymentsCollector.sol"; // -- Agreement state flags -- -// REGISTERED, ACCEPTED are monotonic (once set, never cleared). -// All other flags are clearable — cleared when pending terms are accepted. /// @dev Offer exists in storage uint16 constant REGISTERED = 1; /// @dev Provider accepted terms uint16 constant ACCEPTED = 2; -/// @dev collectableUntil has been reduced, collection capped (clearable) +/// @dev The agreement's collection window has been truncated (e.g. by cancellation). +/// Paired with a BY_* flag identifying the origin. uint16 constant NOTICE_GIVEN = 4; -/// @dev Nothing to collect in current state (clearable — cleared on new terms promotion) +/// @dev Nothing to collect in current state uint16 constant SETTLED = 8; -// -- Who-initiated flags (clearable, meaningful when NOTICE_GIVEN is set) -- +// -- Who-initiated flags (meaningful when NOTICE_GIVEN is set) -- -/// @dev Notice given by payer +/// @dev NOTICE_GIVEN originated from the payer. uint16 constant BY_PAYER = 16; -/// @dev Notice given by provider (forfeit — immediate SETTLED) +/// @dev NOTICE_GIVEN originated from the service provider. uint16 constant BY_PROVIDER = 32; -/// @dev Notice given by data service -uint16 constant BY_DATA_SERVICE = 64; // -- Update-origin flag -- @@ -32,16 +29,6 @@ uint16 constant BY_DATA_SERVICE = 64; /// ORed into returned state by getAgreementDetails for pending versions (index 1). uint16 constant UPDATE = 128; -// -- Togglable option flags (set via accept options parameter) -- - -/// @dev Provider opts in to automatic update on final collect -uint16 constant AUTO_UPDATE = 256; - -// -- Lifecycle flags (set by the collector during auto-update, clearable) -- - -/// @dev Active terms were promoted via auto-update (not explicit provider accept) -uint16 constant AUTO_UPDATED = 512; - // -- Offer type constants -- /// @dev No stored offer — sentinel returned by {IAgreementCollector.getAgreementOfferAt} @@ -59,13 +46,6 @@ uint8 constant SCOPE_ACTIVE = 1; /// @dev Cancel targets pending offers uint8 constant SCOPE_PENDING = 2; -// -- Offer option constants (for unsigned offer path) -- - -/// @dev Reduce collectableUntil and set NOTICE_GIVEN | BY_PAYER on the agreement -uint16 constant WITH_NOTICE = 1; -/// @dev Revert if the targeted version has already been accepted -uint16 constant IF_NOT_ACCEPTED = 2; - /** * @title Base interface for agreement-based payment collectors * @notice Base interface for agreement-based payment collectors. @@ -115,7 +95,8 @@ interface IAgreementCollector is IPaymentsCollector { * @notice Offer a new agreement or update an existing one. * @param offerType The type of offer (OFFER_TYPE_NEW or OFFER_TYPE_UPDATE) * @param data ABI-encoded offer data - * @param options Bitmask of offer options + * @param options Bitmask reserved for implementation-specific options; pass 0 when none apply. + * No flags are defined at the interface level. * @return Agreement details including participants and version hash */ function offer(uint8 offerType, bytes calldata data, uint16 options) external returns (AgreementDetails memory); diff --git a/packages/issuance/audits/PR1301/TRST-R-11.md b/packages/issuance/audits/PR1301/TRST-R-11.md index 014f20625..ae93fd58d 100644 --- a/packages/issuance/audits/PR1301/TRST-R-11.md +++ b/packages/issuance/audits/PR1301/TRST-R-11.md @@ -5,3 +5,9 @@ ## Description `IAgreementCollector` defines state flag constants that are not currently used in the RecurringCollector implementation, including `NOTICE_GIVEN`, `SETTLED`, `BY_PAYER`, `BY_PROVIDER`, `BY_DATA_SERVICE`, `AUTO_UPDATE`, and `AUTO_UPDATED`. Unused public interface constants are a source of confusion for integrators, who may code against documented semantics that the implementation does not honor. Either remove the unused flags from the interface, or implement the behaviors they describe in the collector. + +--- + +Removed usused flags: `AUTO_UPDATE`, `AUTO_UPDATED`, `BY_DATA_SERVICE`, `WITH_NOTICE` and `IF_NOT_ACCEPTED` are dropped from the interface. + +NatSpec updated for remaining flags with new semantics. From 5345ec13cbb390580c21e658e7adee0c10ee63a3 Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:26:20 +0000 Subject: [PATCH 11/31] docs(audit): acknowledge trust-boundary correction in TRST-H-4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v02 mitigation review corrected the security-boundary framing in our fix comment: an EIP-7702 EOA can toggle code on and off across calls, so "an EOA cannot pass the interface check" is not a durable guarantee. The correct boundary is that a provider opting into CONDITION_ELIGIBILITY_CHECK is trusting the payer contract. Recorded the acknowledgement in the team response — no code change required, since the gate already depends on the provider's opt-in. --- packages/issuance/audits/PR1301/TRST-H-4.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/issuance/audits/PR1301/TRST-H-4.md b/packages/issuance/audits/PR1301/TRST-H-4.md index dda0b4f17..d9fa550bc 100644 --- a/packages/issuance/audits/PR1301/TRST-H-4.md +++ b/packages/issuance/audits/PR1301/TRST-H-4.md @@ -29,4 +29,4 @@ Fixed under the assumption that a provider setting `CONDITION_ELIGIBILITY_CHECK` --- -Eligibility checks are now opt-in via the `CONDITION_ELIGIBILITY_CHECK` flag, set explicitly in the agreement terms. Providers agree to eligibility gating by accepting an agreement that includes this condition. When the flag is set, the payer must pass an ERC-165 `supportsInterface` check for `IProviderEligibility` at offer time. An EOA cannot pass this check, so an EOA cannot create an agreement with eligibility gating enabled. +Agreed; the security boundary is that a provider opts into `CONDITION_ELIGIBILITY_CHECK` to trust the payer contract. From ac65e1f486573a25f382a71120c1057b5e545a16 Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:51:35 +0000 Subject: [PATCH 12/31] docs(audit): acknowledge reclaim-reason change in TRST-R-13 STALE_POI is the correct reason for the resize-based stale-allocation path (allocation stays open as stakeless, not closed). The previous CLOSE_ALLOCATION behavior never shipped to production, so there is no operator configuration to migrate. --- packages/issuance/audits/PR1301/TRST-R-13.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/issuance/audits/PR1301/TRST-R-13.md b/packages/issuance/audits/PR1301/TRST-R-13.md index 6b9b090c0..cefb73ec0 100644 --- a/packages/issuance/audits/PR1301/TRST-R-13.md +++ b/packages/issuance/audits/PR1301/TRST-R-13.md @@ -5,3 +5,7 @@ ## Description Before the PR's refactor, `forceCloseStaleAllocation()` closed the allocation via `_closeAllocation()` and caused a reclaim with reason `CLOSE_ALLOCATION`. Post refactor, the force close path goes through `_resizeAllocation(allocationId, 0, ...)`, which triggers a reclaim with reason `STALE_POI` instead. The reclaim still occurs, but the reason code exposed to reclaim address configuration changes. Document this change so that operators are able to prepare accordingly and have funding paths line up with intention. + +--- + +Noted. The previous `CLOSE_ALLOCATION` reclaim behavior for this path has not shipped to production, so there is no live operator configuration to migrate. `STALE_POI` is the correct reason for the post-refactor semantics (the allocation is stale; it stays open as stakeless rather than closing). From 8ed95e41cf4c977d0efbd69ef8420ee4ed489db2 Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:55:40 +0000 Subject: [PATCH 13/31] docs(ram): document collector replay-protection assumption (TRST-R-4) RAM trusts collectors to enforce agreement uniqueness and state transitions. Future collectors must implement their own replay protection on acceptance. --- packages/issuance/audits/PR1301/TRST-R-4.md | 4 ++++ .../contracts/agreement/RecurringAgreementManager.sol | 3 +++ 2 files changed, 7 insertions(+) diff --git a/packages/issuance/audits/PR1301/TRST-R-4.md b/packages/issuance/audits/PR1301/TRST-R-4.md index 6e40e6682..7947adbdc 100644 --- a/packages/issuance/audits/PR1301/TRST-R-4.md +++ b/packages/issuance/audits/PR1301/TRST-R-4.md @@ -5,3 +5,7 @@ ## Description The `approveAgreement()` view checks if the agreement hash is valid, however it offers no replay protection for repeated agreement approvals. This attack vector is only stopped at the RecurringCollector as it checks the agreement does not exist and maintains unidirectional transitions from the agreement Accepted state. For future collectors this may not be the case, necessitating clear documentation of the assumption. + +--- + +Documented in the `RecurringAgreementManager` contract header (collector-trust section): collectors own agreement uniqueness, replay protection, and state transitions; RAM does not re-check them. diff --git a/packages/issuance/contracts/agreement/RecurringAgreementManager.sol b/packages/issuance/contracts/agreement/RecurringAgreementManager.sol index 6edf82117..141d58dcb 100644 --- a/packages/issuance/contracts/agreement/RecurringAgreementManager.sol +++ b/packages/issuance/contracts/agreement/RecurringAgreementManager.sol @@ -57,6 +57,9 @@ import { ReentrancyGuardTransient } from "@openzeppelin/contracts/utils/Reentran * {forceRemoveAgreement} as an operator escape hatch. Once tracked, reconciliation proceeds * even if COLLECTOR_ROLE is later revoked, ensuring orderly settlement. * + * Collectors own agreement uniqueness, replay protection, and state transitions; this + * contract does not re-check them. + * * {offerAgreement} and {cancelAgreement} forward to the collector then reconcile locally. * The collector does not callback to `msg.sender`, so these methods own the full call * sequence and hold the reentrancy lock for the entire operation. From 6a65df7d615479af1c9966bf8c20a29e2502da3d Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:55:59 +0000 Subject: [PATCH 14/31] docs(ram): document non-retroactive role-change semantics (TRST-R-10) Revoking COLLECTOR_ROLE or DATA_SERVICE_ROLE does not invalidate tracked agreements; reconciliation proceeds to orderly settlement. Role checks gate only new offerAgreement calls and discovery inside _reconcileAgreement. --- packages/issuance/audits/PR1301/TRST-R-10.md | 4 ++++ .../contracts/agreement/RecurringAgreementManager.sol | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/issuance/audits/PR1301/TRST-R-10.md b/packages/issuance/audits/PR1301/TRST-R-10.md index 219698e5f..e1d200ba7 100644 --- a/packages/issuance/audits/PR1301/TRST-R-10.md +++ b/packages/issuance/audits/PR1301/TRST-R-10.md @@ -5,3 +5,7 @@ ## Description Changes to `DATA_SERVICE_ROLE` and `COLLECTOR_ROLE` on the RecurringAgreementManager do not affect agreements that have already been offered or accepted through the previously authorized addresses. This is by design (revoking a role should not invalidate settled obligations), but the behavior is not documented. Record this invariant in the RAM documentation so that operators and integrators understand the effect of role changes. + +--- + +Documented in the `RecurringAgreementManager` contract header: role changes are not retroactive — revoking `COLLECTOR_ROLE` or `DATA_SERVICE_ROLE` does not invalidate tracked agreements, which continue to reconcile to orderly settlement. Role checks gate only new `offerAgreement` calls and discovery inside `_reconcileAgreement`. diff --git a/packages/issuance/contracts/agreement/RecurringAgreementManager.sol b/packages/issuance/contracts/agreement/RecurringAgreementManager.sol index 141d58dcb..ab0dbd8f0 100644 --- a/packages/issuance/contracts/agreement/RecurringAgreementManager.sol +++ b/packages/issuance/contracts/agreement/RecurringAgreementManager.sol @@ -54,12 +54,16 @@ import { ReentrancyGuardTransient } from "@openzeppelin/contracts/utils/Reentran * and {cancelAgreement} call collectors directly. Discovery calls `getAgreementDetails`; * reconciliation calls `getMaxNextClaim` — these return values drive escrow accounting. * A broken or malicious collector can cause reconciliation to revert; use - * {forceRemoveAgreement} as an operator escape hatch. Once tracked, reconciliation proceeds - * even if COLLECTOR_ROLE is later revoked, ensuring orderly settlement. + * {forceRemoveAgreement} as an operator escape hatch. * * Collectors own agreement uniqueness, replay protection, and state transitions; this * contract does not re-check them. * + * Role changes are not retroactive. Revoking COLLECTOR_ROLE or DATA_SERVICE_ROLE does not + * invalidate agreements that were offered or accepted while the roles were held. Once + * tracked, reconciliation proceeds to orderly settlement. Role changes only gate *new* + * {offerAgreement} calls and discovery inside {_reconcileAgreement}. + * * {offerAgreement} and {cancelAgreement} forward to the collector then reconcile locally. * The collector does not callback to `msg.sender`, so these methods own the full call * sequence and hold the reentrancy lock for the entire operation. From 98ebd20c283151d16ac644103eae6e5322815e30 Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:56:16 +0000 Subject: [PATCH 15/31] docs(ram): align pause-escalation prose with whenNotPaused scope (TRST-R-8) Escalation ladder item 3 now refers to the existing cross-contract note so the prose matches the whenNotPaused scope on beforeCollection and afterCollection. --- packages/issuance/audits/PR1301/TRST-R-8.md | 4 ++++ .../contracts/agreement/RecurringAgreementManager.sol | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/issuance/audits/PR1301/TRST-R-8.md b/packages/issuance/audits/PR1301/TRST-R-8.md index dd2ea9619..821e84823 100644 --- a/packages/issuance/audits/PR1301/TRST-R-8.md +++ b/packages/issuance/audits/PR1301/TRST-R-8.md @@ -5,3 +5,7 @@ ## Description The RecurringAgreementManager documentation header states that pausing the contract "stops all permissionless escrow management". In practice, the `whenNotPaused` modifier also applies to `beforeCollection()` and `afterCollection()`, so pause also halts the callback path used during `collect()`. Update the documentation to reflect that callbacks are affected, or narrow the modifier application so that behavior matches the prose. + +--- + +Updated in the `RecurringAgreementManager` contract header: pause is described as blocking permissionless state changes "including collection callbacks and reconciliation", with a cross-reference to the existing cross-contract note describing the resulting escrow-accounting drift. diff --git a/packages/issuance/contracts/agreement/RecurringAgreementManager.sol b/packages/issuance/contracts/agreement/RecurringAgreementManager.sol index ab0dbd8f0..a5f3c40b0 100644 --- a/packages/issuance/contracts/agreement/RecurringAgreementManager.sol +++ b/packages/issuance/contracts/agreement/RecurringAgreementManager.sol @@ -84,7 +84,8 @@ import { ReentrancyGuardTransient } from "@openzeppelin/contracts/utils/Reentran * Escalation ladder (targeted → full stop): * 1. {emergencyRevokeRole} — disable a specific actor (operator, collector, guardian) * 2. {emergencyClearEligibilityOracle} — fail-open if oracle blocks collections - * 3. Pause this contract — stops all permissionless escrow management + * 3. Pause this contract — blocks permissionless state changes, including collection + * callbacks and reconciliation (see cross-contract note above) * 4. Pause RecurringCollector — stops all collections and state changes * 5. Pause both — full halt * From 879489db993fb3bb7b0d7ca64bc9f59e32d19a54 Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:56:22 +0000 Subject: [PATCH 16/31] docs(collector): note self-authorization auth-check obligation (TRST-R-9) RC overrides _isAuthorized to return true when signer == address(this), so RC itself must perform the appropriate authorization check before any external call it initiates. --- .../contracts/payments/collectors/RecurringCollector.sol | 5 +++++ packages/horizon/test/unit/utilities/Authorizable.t.sol | 6 +++++- packages/issuance/audits/PR1301/TRST-R-9.md | 4 ++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol index d8a8c63c1..3237a0605 100644 --- a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol +++ b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol @@ -35,6 +35,11 @@ import { PPMMath } from "../../libraries/PPMMath.sol"; * @author Edge & Node * @dev Implements the {IRecurringCollector} interface. * @notice A payments collector contract that can be used to collect payments using a RCA (Recurring Collection Agreement). + * + * @custom:security Self-authorization: RC overrides {_isAuthorized} to return true whenever + * `signer == address(this)`, so RC itself must perform the appropriate authorization check + * before any external call. + * * @custom:security-contact Please email security+contracts@thegraph.com if you find any * bugs. We may have an active bug bounty program. */ diff --git a/packages/horizon/test/unit/utilities/Authorizable.t.sol b/packages/horizon/test/unit/utilities/Authorizable.t.sol index 18ed8df54..c9f47fcba 100644 --- a/packages/horizon/test/unit/utilities/Authorizable.t.sol +++ b/packages/horizon/test/unit/utilities/Authorizable.t.sol @@ -326,7 +326,11 @@ contract AuthorizableTest is Test, Bounder { authorizable.revokeAuthorizedSigner(signer); } - function test_IsAuthorized_Revert_WhenZero(address signer) public view { + function test_IsAuthorized_Revert_WhenZero(address signer) public { + // Subclasses (e.g. RecurringCollector) may treat specific addresses — notably + // the contract itself — as authorized regardless of the authorizer, so rely on + // assumeValidFuzzAddress to exclude those. + assumeValidFuzzAddress(signer); authHelper.assertNotAuthorized(address(0), signer); } } diff --git a/packages/issuance/audits/PR1301/TRST-R-9.md b/packages/issuance/audits/PR1301/TRST-R-9.md index b78e271fe..efa601a43 100644 --- a/packages/issuance/audits/PR1301/TRST-R-9.md +++ b/packages/issuance/audits/PR1301/TRST-R-9.md @@ -5,3 +5,7 @@ ## Description The `_isAuthorized(address authorizer, address signer)` override in RecurringCollector returns true whenever `signer == address(this)`, regardless of `authorizer`. This enables RecurringCollector to call `dataService.cancelIndexingAgreementByPayer()` on the payer's behalf. The semantics are safe in the current integration with SubgraphService, but they widen the trust surface: any future consumer that relies on `RecurringCollector.isAuthorized()` for access control will grant access when the signer is the collector itself. Consider tightening the override to scope trust to specific callers, or explicitly document the integration contract so it is not misapplied by future consumers. + +--- + +Added a `@custom:security` note at the `RecurringCollector` contract header: self-authorization requires the collector itself to perform the appropriate authorization check before any external call. From 244d49eb9c68237b88314f75adcf7b21a964d429 Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:09:24 +0000 Subject: [PATCH 17/31] fix(subgraph-service): validate update terms against RCAU rate, not stale agreement rate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit IndexingAgreement.update() validated new indexing terms against wrapper.collectorAgreement.maxOngoingTokensPerSecond (the current agreement's rate) instead of rcau.maxOngoingTokensPerSecond (the update's rate). If the RCAU decreased the rate, indexing terms exceeding the new cap would be accepted. accept() already validates against rca.maxOngoingTokensPerSecond — this makes update() consistent. --- .../contracts/libraries/IndexingAgreement.sol | 2 +- .../subgraphService/indexing-agreement/shared.t.sol | 10 +++------- .../subgraphService/indexing-agreement/update.t.sol | 6 +++--- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/subgraph-service/contracts/libraries/IndexingAgreement.sol b/packages/subgraph-service/contracts/libraries/IndexingAgreement.sol index 1aa2b9677..347eed37e 100644 --- a/packages/subgraph-service/contracts/libraries/IndexingAgreement.sol +++ b/packages/subgraph-service/contracts/libraries/IndexingAgreement.sol @@ -396,7 +396,7 @@ library IndexingAgreement { metadata.version == IIndexingAgreement.IndexingAgreementVersion.V1, IndexingAgreementInvalidVersion(metadata.version) ); - _setTermsV1(self, rcau.agreementId, metadata.terms, wrapper.collectorAgreement.maxOngoingTokensPerSecond); + _setTermsV1(self, rcau.agreementId, metadata.terms, rcau.maxOngoingTokensPerSecond); emit IndexingAgreementUpdated({ indexer: wrapper.collectorAgreement.serviceProvider, diff --git a/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/shared.t.sol b/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/shared.t.sol index cd35f4aa0..c4d84d705 100644 --- a/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/shared.t.sol +++ b/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/shared.t.sol @@ -294,13 +294,14 @@ contract SubgraphServiceIndexingAgreementSharedTest is SubgraphServiceTest, Boun _rca.deadline, _rca.nonce ); + rcau = _recurringCollectorHelper.sensibleRCAU(rcau); rcau.metadata = _encodeUpdateIndexingAgreementMetadataV1( _newUpdateIndexingAgreementMetadataV1( - bound(_ctx.ctxInternal.seed.termsV1.tokensPerSecond, 0, _rca.maxOngoingTokensPerSecond), + bound(_ctx.ctxInternal.seed.termsV1.tokensPerSecond, 0, rcau.maxOngoingTokensPerSecond), _ctx.ctxInternal.seed.termsV1.tokensPerEntityPerSecond ) ); - return _recurringCollectorHelper.sensibleRCAU(rcau); + return rcau; } function _requireIndexer(Context storage _ctx, address _indexer) internal view returns (IndexerState memory) { @@ -448,10 +449,5 @@ contract SubgraphServiceIndexingAgreementSharedTest is SubgraphServiceTest, Boun assertEq(_expected.dataService, _actual.collectorAgreement.dataService); assertEq(_expected.payer, _actual.collectorAgreement.payer); assertEq(_expected.serviceProvider, _actual.collectorAgreement.serviceProvider); - assertEq(_expected.endsAt, _actual.collectorAgreement.endsAt); - assertEq(_expected.maxInitialTokens, _actual.collectorAgreement.maxInitialTokens); - assertEq(_expected.maxOngoingTokensPerSecond, _actual.collectorAgreement.maxOngoingTokensPerSecond); - assertEq(_expected.minSecondsPerCollection, _actual.collectorAgreement.minSecondsPerCollection); - assertEq(_expected.maxSecondsPerCollection, _actual.collectorAgreement.maxSecondsPerCollection); } } diff --git a/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/update.t.sol b/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/update.t.sol index 321c26df0..9f1abc180 100644 --- a/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/update.t.sol +++ b/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/update.t.sol @@ -166,10 +166,10 @@ contract SubgraphServiceIndexingAgreementUpgradeTest is SubgraphServiceIndexingA indexerState ); - // Create update with tokensPerSecond exceeding the RCA's maxOngoingTokensPerSecond - uint256 excessiveTokensPerSecond = acceptedRca.maxOngoingTokensPerSecond + 1; + // Create update with tokensPerSecond exceeding the RCAU's maxOngoingTokensPerSecond IRecurringCollector.RecurringCollectionAgreementUpdate memory rcau = _generateAcceptableRecurringCollectionAgreementUpdate(ctx, acceptedRca); + uint256 excessiveTokensPerSecond = rcau.maxOngoingTokensPerSecond + 1; rcau.metadata = _encodeUpdateIndexingAgreementMetadataV1( IndexingAgreement.UpdateIndexingAgreementMetadata({ version: IIndexingAgreement.IndexingAgreementVersion.V1, @@ -190,7 +190,7 @@ contract SubgraphServiceIndexingAgreementUpgradeTest is SubgraphServiceIndexingA bytes memory expectedErr = abi.encodeWithSelector( IndexingAgreement.IndexingAgreementInvalidTerms.selector, excessiveTokensPerSecond, - acceptedRca.maxOngoingTokensPerSecond + rcau.maxOngoingTokensPerSecond ); vm.expectRevert(expectedErr); resetPrank(indexerState.addr); From 498b1742013384f8635bdff49485dec5b95fde66 Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Wed, 22 Apr 2026 08:03:24 +0000 Subject: [PATCH 18/31] refactor(collector): preparatory cleanups Assorted small refactors and interface tweaks that set the stage for the hash-keyed terms work without changing behavior: - extract _rcaIdAndHash helper (agreement ID + RCA hash used together) - default _getMaxNextClaimScoped scope to both (active | pending) on 0 - drop redundant isSigned param from _requireAuthorization - drop redundant timestamp from agreement lifecycle events - single-line AgreementCanceled emit - add VERSION_CURRENT/VERSION_NEXT constants and clarify state flag NatSpec in IAgreementCollector --- .../collectors/RecurringCollector.sol | 77 +++++++++---------- .../recurring-collector/acceptUnsigned.t.sol | 1 - .../recurring-collector/mixedPath.t.sol | 2 - .../payments/recurring-collector/shared.t.sol | 2 - .../payments/recurring-collector/update.t.sol | 1 - .../recurring-collector/updateUnsigned.t.sol | 1 - .../contracts/horizon/IAgreementCollector.sol | 67 ++++++++++++---- .../contracts/horizon/IRecurringCollector.sol | 6 -- .../indexing-agreement/integration.t.sol | 1 - 9 files changed, 88 insertions(+), 70 deletions(-) diff --git a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol index 3237a0605..904cabebe 100644 --- a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol +++ b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol @@ -211,7 +211,7 @@ contract RecurringCollector is function accept( RecurringCollectionAgreement calldata rca, bytes calldata signature - ) external whenNotPaused returns (bytes16) { + ) external whenNotPaused returns (bytes16 agreementId) { /* solhint-disable gas-strict-inequalities */ require( rca.deadline >= block.timestamp, @@ -219,17 +219,10 @@ contract RecurringCollector is ); /* solhint-enable gas-strict-inequalities */ - bool isSigned = 0 < signature.length; - bytes32 rcaHash = _hashRCA(rca); - bytes16 agreementId = _generateAgreementId( - rca.payer, - rca.dataService, - rca.serviceProvider, - rca.deadline, - rca.nonce - ); + bytes32 rcaHash; + (agreementId, rcaHash) = _rcaIdAndHash(rca); - _requireAuthorization(rca.payer, rcaHash, signature, isSigned, agreementId, OFFER_TYPE_NEW); + _requireAuthorization(rca.payer, rcaHash, signature, agreementId, OFFER_TYPE_NEW); return _validateAndStoreAgreement(rca, agreementId, rcaHash); } @@ -286,7 +279,6 @@ contract RecurringCollector is agreement.payer, agreement.serviceProvider, agreementId, - agreement.acceptedAt, agreement.endsAt, agreement.maxInitialTokens, agreement.maxOngoingTokensPerSecond, @@ -321,14 +313,7 @@ contract RecurringCollector is agreement.state = AgreementState.CanceledByServiceProvider; } - emit AgreementCanceled( - agreement.dataService, - agreement.payer, - agreement.serviceProvider, - agreementId, - agreement.canceledAt, - by - ); + emit AgreementCanceled(agreement.dataService, agreement.payer, agreement.serviceProvider, agreementId, by); } /** @@ -348,10 +333,9 @@ contract RecurringCollector is ); /* solhint-enable gas-strict-inequalities */ - bool isSigned = 0 < signature.length; bytes32 rcauHash = _hashRCAU(rcau); - _requireAuthorization(agreement.payer, rcauHash, signature, isSigned, rcau.agreementId, OFFER_TYPE_UPDATE); + _requireAuthorization(agreement.payer, rcauHash, signature, rcau.agreementId, OFFER_TYPE_UPDATE); _validateAndStoreUpdate(agreement, rcau, rcauHash); } @@ -396,7 +380,7 @@ contract RecurringCollector is /// @inheritdoc IAgreementCollector function getMaxNextClaim(bytes16 agreementId) external view returns (uint256) { - return _getMaxNextClaimScoped(agreementId, SCOPE_ACTIVE | SCOPE_PENDING); + return _getMaxNextClaimScoped(agreementId, 0); } /// @inheritdoc IRecurringCollector @@ -434,25 +418,18 @@ contract RecurringCollector is require(msg.sender == rca.payer, RecurringCollectorUnauthorizedCaller(msg.sender, rca.payer)); _requirePayerToSupportEligibilityCheck(rca.payer, rca.conditions); - bytes16 agreementId = _generateAgreementId( - rca.payer, - rca.dataService, - rca.serviceProvider, - rca.deadline, - rca.nonce - ); - bytes32 offerHash = _hashRCA(rca); + (bytes16 agreementId, bytes32 rcaHash) = _rcaIdAndHash(rca); - $.rcaOffers[agreementId] = StoredOffer({ offerHash: offerHash, data: _data }); + $.rcaOffers[agreementId] = StoredOffer({ offerHash: rcaHash, data: _data }); details.agreementId = agreementId; details.payer = rca.payer; details.dataService = rca.dataService; details.serviceProvider = rca.serviceProvider; - details.versionHash = offerHash; + details.versionHash = rcaHash; details.state = REGISTERED; - emit OfferStored(agreementId, rca.payer, OFFER_TYPE_NEW, offerHash); + emit OfferStored(agreementId, rca.payer, OFFER_TYPE_NEW, rcaHash); } /** @@ -975,8 +952,7 @@ contract RecurringCollector is * @notice Verifies authorization for an EIP712 hash using the given basis. * @param _payer The payer address (signer owner for ECDSA, contract for approval) * @param _hash The EIP712 typed data hash - * @param _signature The ECDSA signature (only used when basis is Signature) - * @param _isSigned True if ECDSA-signed, false if pre-approved via stored offer + * @param _signature The ECDSA signature bytes, zero length for no signature (pre-approved via stored offer) * @param _agreementId The agreement ID (used to look up stored offer when not signed) * @param _offerType OFFER_TYPE_NEW or OFFER_TYPE_UPDATE (selects which stored offer to check) */ @@ -984,13 +960,12 @@ contract RecurringCollector is address _payer, bytes32 _hash, bytes memory _signature, - bool _isSigned, bytes16 _agreementId, uint8 _offerType ) private view { RecurringCollectorStorage storage $ = _getStorage(); - if (_isSigned) + if (0 < _signature.length) require(_isAuthorized(_payer, ECDSA.recover(_hash, _signature)), RecurringCollectorInvalidSigner()); else // Check stored offer hash instead of callback @@ -1066,7 +1041,6 @@ contract RecurringCollector is _agreement.payer, _agreement.serviceProvider, _rcau.agreementId, - uint64(block.timestamp), _agreement.endsAt, _agreement.maxInitialTokens, _agreement.maxOngoingTokensPerSecond, @@ -1162,8 +1136,7 @@ contract RecurringCollector is // Only Accepted and CanceledByPayer are collectable if (_a.state != AgreementState.Accepted && _a.state != AgreementState.CanceledByPayer) return 0; - // Collection starts from last collection (or acceptance if never collected) - uint256 collectionStart = 0 < _a.lastCollectionAt ? _a.lastCollectionAt : _a.acceptedAt; + uint256 collectionStart = _agreementCollectionStartAt(_a); // Determine the latest possible collection end uint256 collectionEnd; @@ -1195,6 +1168,8 @@ contract RecurringCollector is * @return maxClaim The maximum tokens claimable under the requested scope */ function _getMaxNextClaimScoped(bytes16 agreementId, uint8 agreementScope) private view returns (uint256 maxClaim) { + if (agreementScope == 0) agreementScope = SCOPE_ACTIVE | SCOPE_PENDING; + RecurringCollectorStorage storage $ = _getStorage(); AgreementData storage _a = $.agreements[agreementId]; @@ -1301,4 +1276,24 @@ contract RecurringCollector is ) private pure returns (bytes16) { return bytes16(keccak256(abi.encode(payer, dataService, serviceProvider, deadline, nonce))); } + + /** + * @notice Compute the agreement ID and EIP-712 hash for an RCA. + * @dev These are always used together when accepting or offering an RCA. + * @param _rca The Recurring Collection Agreement + * @return agreementId The deterministic agreement ID + * @return rcaHash The EIP-712 hash of the RCA + */ + function _rcaIdAndHash( + RecurringCollectionAgreement memory _rca + ) private view returns (bytes16 agreementId, bytes32 rcaHash) { + agreementId = _generateAgreementId( + _rca.payer, + _rca.dataService, + _rca.serviceProvider, + _rca.deadline, + _rca.nonce + ); + rcaHash = _hashRCA(_rca); + } } diff --git a/packages/horizon/test/unit/payments/recurring-collector/acceptUnsigned.t.sol b/packages/horizon/test/unit/payments/recurring-collector/acceptUnsigned.t.sol index 7feca10c9..fb26e3d99 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/acceptUnsigned.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/acceptUnsigned.t.sol @@ -60,7 +60,6 @@ contract RecurringCollectorAcceptUnsignedTest is RecurringCollectorSharedTest { rca.payer, rca.serviceProvider, expectedId, - uint64(block.timestamp), rca.endsAt, rca.maxInitialTokens, rca.maxOngoingTokensPerSecond, diff --git a/packages/horizon/test/unit/payments/recurring-collector/mixedPath.t.sol b/packages/horizon/test/unit/payments/recurring-collector/mixedPath.t.sol index f81aa0f04..120214815 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/mixedPath.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/mixedPath.t.sol @@ -64,7 +64,6 @@ contract RecurringCollectorMixedPathTest is RecurringCollectorSharedTest { address(approver), rca.serviceProvider, agreementId, - uint64(block.timestamp), rcau.endsAt, rcau.maxInitialTokens, rcau.maxOngoingTokensPerSecond, @@ -185,7 +184,6 @@ contract RecurringCollectorMixedPathTest is RecurringCollectorSharedTest { payer, rca.serviceProvider, agreementId, - uint64(block.timestamp), rcau.endsAt, rcau.maxInitialTokens, rcau.maxOngoingTokensPerSecond, diff --git a/packages/horizon/test/unit/payments/recurring-collector/shared.t.sol b/packages/horizon/test/unit/payments/recurring-collector/shared.t.sol index 3e88525e9..2d90e7142 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/shared.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/shared.t.sol @@ -120,7 +120,6 @@ contract RecurringCollectorSharedTest is Test, Bounder { _rca.payer, _rca.serviceProvider, expectedAgreementId, - uint64(block.timestamp), _rca.endsAt, _rca.maxInitialTokens, _rca.maxOngoingTokensPerSecond, @@ -165,7 +164,6 @@ contract RecurringCollectorSharedTest is Test, Bounder { _rca.payer, _rca.serviceProvider, _agreementId, - uint64(block.timestamp), _by ); vm.prank(_rca.dataService); diff --git a/packages/horizon/test/unit/payments/recurring-collector/update.t.sol b/packages/horizon/test/unit/payments/recurring-collector/update.t.sol index be84dde2f..57e8f0ad3 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/update.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/update.t.sol @@ -143,7 +143,6 @@ contract RecurringCollectorUpdateTest is RecurringCollectorSharedTest { acceptedRca.payer, acceptedRca.serviceProvider, rcau.agreementId, - uint64(block.timestamp), rcau.endsAt, rcau.maxInitialTokens, rcau.maxOngoingTokensPerSecond, diff --git a/packages/horizon/test/unit/payments/recurring-collector/updateUnsigned.t.sol b/packages/horizon/test/unit/payments/recurring-collector/updateUnsigned.t.sol index 45d05c55b..84eab9b75 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/updateUnsigned.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/updateUnsigned.t.sol @@ -87,7 +87,6 @@ contract RecurringCollectorUpdateUnsignedTest is RecurringCollectorSharedTest { rca.payer, rca.serviceProvider, agreementId, - uint64(block.timestamp), rcau.endsAt, rcau.maxInitialTokens, rcau.maxOngoingTokensPerSecond, diff --git a/packages/interfaces/contracts/horizon/IAgreementCollector.sol b/packages/interfaces/contracts/horizon/IAgreementCollector.sol index f26622adc..1654e2ff3 100644 --- a/packages/interfaces/contracts/horizon/IAgreementCollector.sol +++ b/packages/interfaces/contracts/horizon/IAgreementCollector.sol @@ -3,11 +3,13 @@ pragma solidity ^0.8.22; import { IPaymentsCollector } from "./IPaymentsCollector.sol"; -// -- Agreement state flags -- +// -- State flags for AgreementDetails -- +// Describe the queried version in context of its agreement; returned by both +// offer() and getAgreementDetails(). See AgreementDetails.state NatSpec. -/// @dev Offer exists in storage +/// @dev Offer exists in storage. Implied by ACCEPTED. uint16 constant REGISTERED = 1; -/// @dev Provider accepted terms +/// @dev Provider accepted terms. Always returned with REGISTERED set (accepted terms were stored). uint16 constant ACCEPTED = 2; /// @dev The agreement's collection window has been truncated (e.g. by cancellation). /// Paired with a BY_* flag identifying the origin. @@ -24,9 +26,8 @@ uint16 constant BY_PROVIDER = 32; // -- Update-origin flag -- -/// @dev Terms originated from an RCAU (update), not the initial RCA. -/// Set on agreement state when active terms come from an accepted or pre-acceptance update. -/// ORed into returned state by getAgreementDetails for pending versions (index 1). +/// @dev This version's terms originated from an update, not the initial agreement offer. +/// Describes the version's provenance; set wherever the update-derived version is returned. uint16 constant UPDATE = 128; // -- Offer type constants -- @@ -46,6 +47,19 @@ uint8 constant SCOPE_ACTIVE = 1; /// @dev Cancel targets pending offers uint8 constant SCOPE_PENDING = 2; +// -- Version indices (shared by getAgreementDetails and getAgreementOfferAt) -- +// +// Versions are enumerated starting at 0. Implementations may expose any number of versions; +// callers iterate until an empty result signals no further versions. These named aliases +// cover the two versions every collector is expected to expose. + +/// @dev The currently-active version: the accepted terms if the agreement is accepted, +/// otherwise the pre-acceptance offer (if any). Empty when no agreement or offer exists. +uint256 constant VERSION_CURRENT = 0; +/// @dev The next queued version: a pending update offer waiting to be accepted. +/// Empty when no queued update exists. +uint256 constant VERSION_NEXT = 1; + /** * @title Base interface for agreement-based payment collectors * @notice Base interface for agreement-based payment collectors. @@ -63,12 +77,22 @@ interface IAgreementCollector is IPaymentsCollector { /** * @notice Agreement details: participants, version hash, and state flags. * Returned by {offer} and {getAgreementDetails}. + * + * The `state` field describes the version identified by `versionHash` in the + * context of its agreement. Flags that depend on the version (REGISTERED, + * ACCEPTED, UPDATE) are set only when they apply to that specific version; + * flags that describe the agreement-wide collectability of that version + * (NOTICE_GIVEN, BY_PAYER, BY_PROVIDER, SETTLED) reflect the current + * agreement state. Identical semantics whether returned by {offer} or + * {getAgreementDetails} — the returned flags always describe the queried + * version. + * * @param agreementId The agreement ID * @param payer The address of the payer * @param dataService The address of the data service * @param serviceProvider The address of the service provider * @param versionHash The EIP-712 hash of the terms at the requested version - * @param state Agreement state flags, with UPDATE set when applicable + * @param state State flags describing the queried version in context of its agreement */ // solhint-disable-next-line gas-struct-packing struct AgreementDetails { @@ -93,6 +117,11 @@ interface IAgreementCollector is IPaymentsCollector { /** * @notice Offer a new agreement or update an existing one. + * @dev Returns {AgreementDetails} for the just-stored offer. The `state` field + * describes that version in context of its agreement (see {AgreementDetails}): + * version-specific flags (REGISTERED, ACCEPTED, UPDATE) are set when they + * apply to the offered version; agreement-wide flags (NOTICE_GIVEN, BY_*, + * SETTLED) reflect current agreement state. * @param offerType The type of offer (OFFER_TYPE_NEW or OFFER_TYPE_UPDATE) * @param data ABI-encoded offer data * @param options Bitmask reserved for implementation-specific options; pass 0 when none apply. @@ -102,17 +131,23 @@ interface IAgreementCollector is IPaymentsCollector { function offer(uint8 offerType, bytes calldata data, uint16 options) external returns (AgreementDetails memory); /** - * @notice Cancel an agreement or revoke a pending update, determined by termsHash. + * @notice Cancel an agreement or revoke a pending offer. + * @dev Scopes can be combined. SCOPE_PENDING and SCOPE_ACTIVE require payer authorization + * and no-op if nothing exists on-chain. * @param agreementId The agreement's ID. - * @param termsHash EIP-712 hash identifying which terms to cancel (active or pending). - * @param options Bitmask — SCOPE_ACTIVE (1) targets active terms, SCOPE_PENDING (2) targets pending offers. + * @param termsHash EIP-712 hash identifying which terms to cancel. + * @param options Bitmask — SCOPE_ACTIVE (1) active terms, SCOPE_PENDING (2) pending offers. */ function cancel(bytes16 agreementId, bytes32 termsHash, uint16 options) external; /** * @notice Get agreement details at a given version index. + * @dev Versions are enumerated from 0. VERSION_CURRENT is the active version (or + * pre-acceptance offer); VERSION_NEXT is the queued pending update, if any. Empty + * details are returned when no version exists at the requested index — callers can + * iterate versions until reaching an empty result. * @param agreementId The ID of the agreement - * @param index The zero-based version index + * @param index Version index (VERSION_CURRENT, VERSION_NEXT, or higher if the implementation supports more) * @return Agreement details including participants, version hash, and state flags */ function getAgreementDetails(bytes16 agreementId, uint256 index) external view returns (AgreementDetails memory); @@ -133,11 +168,13 @@ interface IAgreementCollector is IPaymentsCollector { function getMaxNextClaim(bytes16 agreementId) external view returns (uint256); /** - * @notice Original offer for a given version, enabling independent access and hash verification. - * @dev Returns the offer type (OFFER_TYPE_NEW or OFFER_TYPE_UPDATE) and the ABI-encoded - * original struct. Callers can decode and hash to verify the stored version hash. + * @notice Original offer data for a given version index, enabling independent access and hash verification. + * @dev Returns the offer type and the ABI-encoded original struct so callers can decode + * and rehash to verify the version hash returned by getAgreementDetails. Version semantics + * mirror getAgreementDetails, but empty data is returned when the version's offer was not + * stored (e.g. signed acceptance without a prior offer(), or overwritten by a later update). * @param agreementId The ID of the agreement - * @param index The zero-based version index + * @param index Version index (VERSION_CURRENT, VERSION_NEXT, or higher if supported) * @return offerType OFFER_TYPE_NEW, OFFER_TYPE_UPDATE, or OFFER_TYPE_NONE when no offer is stored * @return offerData ABI-encoded original offer struct, or empty when offerType is OFFER_TYPE_NONE */ diff --git a/packages/interfaces/contracts/horizon/IRecurringCollector.sol b/packages/interfaces/contracts/horizon/IRecurringCollector.sol index 33501f940..6315033e2 100644 --- a/packages/interfaces/contracts/horizon/IRecurringCollector.sol +++ b/packages/interfaces/contracts/horizon/IRecurringCollector.sol @@ -164,7 +164,6 @@ interface IRecurringCollector is IAuthorizable, IAgreementCollector { * @param payer The address of the payer * @param serviceProvider The address of the service provider * @param agreementId The agreement ID - * @param acceptedAt The timestamp when the agreement was accepted * @param endsAt The timestamp when the agreement ends * @param maxInitialTokens The maximum amount of tokens that can be collected in the first collection * @param maxOngoingTokensPerSecond The maximum amount of tokens that can be collected per second @@ -176,7 +175,6 @@ interface IRecurringCollector is IAuthorizable, IAgreementCollector { address indexed payer, address indexed serviceProvider, bytes16 agreementId, - uint64 acceptedAt, uint64 endsAt, uint256 maxInitialTokens, uint256 maxOngoingTokensPerSecond, @@ -190,7 +188,6 @@ interface IRecurringCollector is IAuthorizable, IAgreementCollector { * @param payer The address of the payer * @param serviceProvider The address of the service provider * @param agreementId The agreement ID - * @param canceledAt The timestamp when the agreement was canceled * @param canceledBy The party that canceled the agreement */ event AgreementCanceled( @@ -198,7 +195,6 @@ interface IRecurringCollector is IAuthorizable, IAgreementCollector { address indexed payer, address indexed serviceProvider, bytes16 agreementId, - uint64 canceledAt, CancelAgreementBy canceledBy ); @@ -208,7 +204,6 @@ interface IRecurringCollector is IAuthorizable, IAgreementCollector { * @param payer The address of the payer * @param serviceProvider The address of the service provider * @param agreementId The agreement ID - * @param updatedAt The timestamp when the agreement was updated * @param endsAt The timestamp when the agreement ends * @param maxInitialTokens The maximum amount of tokens that can be collected in the first collection * @param maxOngoingTokensPerSecond The maximum amount of tokens that can be collected per second @@ -220,7 +215,6 @@ interface IRecurringCollector is IAuthorizable, IAgreementCollector { address indexed payer, address indexed serviceProvider, bytes16 agreementId, - uint64 updatedAt, uint64 endsAt, uint256 maxInitialTokens, uint256 maxOngoingTokensPerSecond, diff --git a/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/integration.t.sol b/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/integration.t.sol index 609a91b46..45f31e527 100644 --- a/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/integration.t.sol +++ b/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/integration.t.sol @@ -139,7 +139,6 @@ contract SubgraphServiceIndexingAgreementIntegrationTest is SubgraphServiceIndex acceptedRca.payer, acceptedRca.serviceProvider, agreementId, - uint64(block.timestamp), IRecurringCollector.CancelAgreementBy.Payer ); From 747ddcb841eef45a1563b6af4de52837c89a70b2 Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:18:30 +0000 Subject: [PATCH 19/31] refactor(collector): drop unreachable agreementId-zero check --- .../horizon/contracts/payments/collectors/RecurringCollector.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol index 904cabebe..136b0ce04 100644 --- a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol +++ b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol @@ -238,7 +238,6 @@ contract RecurringCollector is bytes16 agreementId, bytes32 _rcaHash ) private returns (bytes16) { - require(agreementId != bytes16(0), RecurringCollectorAgreementIdZero()); require(msg.sender == _rca.dataService, RecurringCollectorUnauthorizedCaller(msg.sender, _rca.dataService)); require( From 4a286e12f705375e7b92f071594a4bd0e4baed21 Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:19:06 +0000 Subject: [PATCH 20/31] refactor(collector): hoist dataService check from _validateAndStoreAgreement to accept() --- .../contracts/payments/collectors/RecurringCollector.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol index 136b0ce04..d251788a1 100644 --- a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol +++ b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol @@ -218,6 +218,7 @@ contract RecurringCollector is RecurringCollectorAgreementDeadlineElapsed(block.timestamp, rca.deadline) ); /* solhint-enable gas-strict-inequalities */ + require(msg.sender == rca.dataService, RecurringCollectorUnauthorizedCaller(msg.sender, rca.dataService)); bytes32 rcaHash; (agreementId, rcaHash) = _rcaIdAndHash(rca); @@ -238,8 +239,6 @@ contract RecurringCollector is bytes16 agreementId, bytes32 _rcaHash ) private returns (bytes16) { - require(msg.sender == _rca.dataService, RecurringCollectorUnauthorizedCaller(msg.sender, _rca.dataService)); - require( _rca.dataService != address(0) && _rca.payer != address(0) && _rca.serviceProvider != address(0), RecurringCollectorAgreementAddressNotSet() From f9bdf46637f34fc8aba1ceef3ecfe69e0ab11d66 Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:29:24 +0000 Subject: [PATCH 21/31] refactor(collector): extract _requireValidTerms from duplicated validation The (window params + eligibility + overflow) triple was duplicated in _validateAndStoreAgreement and _validateAndStoreUpdate. Extract into _requireValidTerms. No behaviour change. --- .../collectors/RecurringCollector.sol | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol index d251788a1..2c41dd6b2 100644 --- a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol +++ b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol @@ -244,9 +244,6 @@ contract RecurringCollector is RecurringCollectorAgreementAddressNotSet() ); - _requireValidCollectionWindowParams(_rca.endsAt, _rca.minSecondsPerCollection, _rca.maxSecondsPerCollection); - _requirePayerToSupportEligibilityCheck(_rca.payer, _rca.conditions); - AgreementData storage agreement = _getAgreementStorage(agreementId); // check that the agreement is not already accepted require( @@ -254,8 +251,10 @@ contract RecurringCollector is RecurringCollectorAgreementIncorrectState(agreementId, agreement.state) ); - // Reverts on overflow — rejecting excessive terms that could prevent collection - _rca.maxOngoingTokensPerSecond * _rca.maxSecondsPerCollection * 1024; + _requireValidTerms( + _rca.endsAt, _rca.minSecondsPerCollection, _rca.maxSecondsPerCollection, + _rca.payer, _rca.conditions, _rca.maxOngoingTokensPerSecond + ); // accept the agreement agreement.acceptedAt = uint64(block.timestamp); @@ -815,6 +814,30 @@ contract RecurringCollector is ); } + /** + * @notice Validates offer terms: collection window, eligibility support, and overflow. + * @dev Called by _validateAndStoreAgreement and _validateAndStoreUpdate. + * @param _endsAt The end time of the agreement + * @param _minSecondsPerCollection The minimum seconds per collection + * @param _maxSecondsPerCollection The maximum seconds per collection + * @param _payer The payer address (for eligibility validation) + * @param _conditions The conditions bitmask + * @param _maxOngoingTokensPerSecond The maximum ongoing tokens per second + */ + function _requireValidTerms( + uint64 _endsAt, + uint32 _minSecondsPerCollection, + uint32 _maxSecondsPerCollection, + address _payer, + uint16 _conditions, + uint256 _maxOngoingTokensPerSecond + ) private view { + _requireValidCollectionWindowParams(_endsAt, _minSecondsPerCollection, _maxSecondsPerCollection); + _requirePayerToSupportEligibilityCheck(_payer, _conditions); + // Reverts on overflow — rejecting excessive terms that could prevent collection + _maxOngoingTokensPerSecond * _maxSecondsPerCollection * 1024; + } + /** * @notice Validates temporal constraints and caps the requested token amount. * @dev Enforces `minSecondsPerCollection` (unless canceled/elapsed) and returns the lesser of @@ -1013,11 +1036,10 @@ contract RecurringCollector is RecurringCollectorInvalidUpdateNonce(_rcau.agreementId, expectedNonce, _rcau.nonce) ); - _requireValidCollectionWindowParams(_rcau.endsAt, _rcau.minSecondsPerCollection, _rcau.maxSecondsPerCollection); - _requirePayerToSupportEligibilityCheck(_agreement.payer, _rcau.conditions); - - // Reverts on overflow — rejecting excessive terms that could prevent collection - _rcau.maxOngoingTokensPerSecond * _rcau.maxSecondsPerCollection * 1024; + _requireValidTerms( + _rcau.endsAt, _rcau.minSecondsPerCollection, _rcau.maxSecondsPerCollection, + _agreement.payer, _rcau.conditions, _rcau.maxOngoingTokensPerSecond + ); // Clean up stored replaced offer — oldHash is always non-zero for accepted agreements bytes32 oldHash = _agreement.activeTermsHash; From 4f93f8db19308ded12d147ad1aae25a76b9d7564 Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:42:54 +0000 Subject: [PATCH 22/31] refactor(collector): split accept logic out of _validateAndStoreAgreement Move the state flip (acceptedAt, state=Accepted) and AgreementAccepted event from _validateAndStoreAgreement into accept() inline. Use rca.* for the event instead of re-reading from storage. The function now only validates and registers (identity + terms). --- .../collectors/RecurringCollector.sol | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol index 2c41dd6b2..c98b4f8da 100644 --- a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol +++ b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol @@ -225,27 +225,47 @@ contract RecurringCollector is _requireAuthorization(rca.payer, rcaHash, signature, agreementId, OFFER_TYPE_NEW); - return _validateAndStoreAgreement(rca, agreementId, rcaHash); + _validateAndStoreAgreement(rca, agreementId, rcaHash); + + AgreementData storage agreement = _getStorage().agreements[agreementId]; + require( + agreement.state == AgreementState.NotAccepted, + RecurringCollectorAgreementIncorrectState(agreementId, agreement.state) + ); + agreement.acceptedAt = uint64(block.timestamp); + agreement.state = AgreementState.Accepted; + + emit AgreementAccepted( + rca.dataService, + rca.payer, + rca.serviceProvider, + agreementId, + rca.endsAt, + rca.maxInitialTokens, + rca.maxOngoingTokensPerSecond, + rca.minSecondsPerCollection, + rca.maxSecondsPerCollection + ); } /** - * @notice Validates RCA fields and stores the agreement. + * @notice Validates RCA fields and registers the agreement (identity + terms). + * Does not flip state to Accepted — caller handles the accept step. * @param _rca The Recurring Collection Agreement to validate and store - * @return agreementId The deterministically generated agreement ID + * @param agreementId The deterministic agreement ID + * @param _rcaHash The EIP-712 hash of the RCA */ - /* solhint-disable function-max-lines */ function _validateAndStoreAgreement( RecurringCollectionAgreement memory _rca, bytes16 agreementId, bytes32 _rcaHash - ) private returns (bytes16) { + ) private { require( _rca.dataService != address(0) && _rca.payer != address(0) && _rca.serviceProvider != address(0), RecurringCollectorAgreementAddressNotSet() ); AgreementData storage agreement = _getAgreementStorage(agreementId); - // check that the agreement is not already accepted require( agreement.state == AgreementState.NotAccepted, RecurringCollectorAgreementIncorrectState(agreementId, agreement.state) @@ -256,9 +276,6 @@ contract RecurringCollector is _rca.payer, _rca.conditions, _rca.maxOngoingTokensPerSecond ); - // accept the agreement - agreement.acceptedAt = uint64(block.timestamp); - agreement.state = AgreementState.Accepted; agreement.dataService = _rca.dataService; agreement.payer = _rca.payer; agreement.serviceProvider = _rca.serviceProvider; @@ -270,22 +287,7 @@ contract RecurringCollector is agreement.conditions = _rca.conditions; agreement.activeTermsHash = _rcaHash; agreement.updateNonce = 0; - - emit AgreementAccepted( - agreement.dataService, - agreement.payer, - agreement.serviceProvider, - agreementId, - agreement.endsAt, - agreement.maxInitialTokens, - agreement.maxOngoingTokensPerSecond, - agreement.minSecondsPerCollection, - agreement.maxSecondsPerCollection - ); - - return agreementId; } - /* solhint-enable function-max-lines */ /** * @inheritdoc IRecurringCollector From 85e40079455a2890deb853614ea132eed89d0bca Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:44:13 +0000 Subject: [PATCH 23/31] refactor(collector): split update apply out of _validateAndStoreUpdate Move nonce check, nonce write, and AgreementUpdated event from _validateAndStoreUpdate into update() inline. Use rcau.* for event fields. The function now only validates terms and writes them to storage; update() handles lifecycle (nonce, event). --- .../collectors/RecurringCollector.sol | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol index c98b4f8da..66c22b5b3 100644 --- a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol +++ b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol @@ -336,7 +336,26 @@ contract RecurringCollector is _requireAuthorization(agreement.payer, rcauHash, signature, rcau.agreementId, OFFER_TYPE_UPDATE); + uint32 expectedNonce = agreement.updateNonce + 1; + require( + rcau.nonce == expectedNonce, + RecurringCollectorInvalidUpdateNonce(rcau.agreementId, expectedNonce, rcau.nonce) + ); + _validateAndStoreUpdate(agreement, rcau, rcauHash); + agreement.updateNonce = rcau.nonce; + + emit AgreementUpdated( + agreement.dataService, + agreement.payer, + agreement.serviceProvider, + rcau.agreementId, + rcau.endsAt, + rcau.maxInitialTokens, + rcau.maxOngoingTokensPerSecond, + rcau.minSecondsPerCollection, + rcau.maxSecondsPerCollection + ); } /// @inheritdoc IRecurringCollector @@ -1031,13 +1050,6 @@ contract RecurringCollector is ) private { RecurringCollectorStorage storage $ = _getStorage(); - // validate nonce to prevent replay attacks - uint32 expectedNonce = _agreement.updateNonce + 1; - require( - _rcau.nonce == expectedNonce, - RecurringCollectorInvalidUpdateNonce(_rcau.agreementId, expectedNonce, _rcau.nonce) - ); - _requireValidTerms( _rcau.endsAt, _rcau.minSecondsPerCollection, _rcau.maxSecondsPerCollection, _agreement.payer, _rcau.conditions, _rcau.maxOngoingTokensPerSecond @@ -1048,7 +1060,7 @@ contract RecurringCollector is if ($.rcaOffers[_rcau.agreementId].offerHash == oldHash) delete $.rcaOffers[_rcau.agreementId]; else if ($.rcauOffers[_rcau.agreementId].offerHash == oldHash) delete $.rcauOffers[_rcau.agreementId]; - // update the agreement + // update the agreement terms _agreement.endsAt = _rcau.endsAt; _agreement.maxInitialTokens = _rcau.maxInitialTokens; _agreement.maxOngoingTokensPerSecond = _rcau.maxOngoingTokensPerSecond; @@ -1056,19 +1068,6 @@ contract RecurringCollector is _agreement.maxSecondsPerCollection = _rcau.maxSecondsPerCollection; _agreement.conditions = _rcau.conditions; _agreement.activeTermsHash = _rcauHash; - _agreement.updateNonce = _rcau.nonce; - - emit AgreementUpdated( - _agreement.dataService, - _agreement.payer, - _agreement.serviceProvider, - _rcau.agreementId, - _agreement.endsAt, - _agreement.maxInitialTokens, - _agreement.maxOngoingTokensPerSecond, - _agreement.minSecondsPerCollection, - _agreement.maxSecondsPerCollection - ); } /** From 9111993a11016428d4a55231110483fe7414cbbf Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:44:38 +0000 Subject: [PATCH 24/31] feat: idempotent accept/update/cancel with allocation rebinding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Align re-accept, re-update, and cancel-on-nothing semantics across RecurringCollector and SubgraphService so duplicate calls with the same signed terms are no-ops rather than reverts, cancel against a nonexistent agreement is a silent no-op, and an accepted agreement can be moved to a different allocation. RecurringCollector - accept(): short-circuits on Accepted state after _validateAndStoreAgreement, so re-accepting the same signed RCA is a no-op. Cancelled agreements still revert — re-accept of a cancelled agreement is never valid. - update(): short-circuits when activeTermsHash already equals the RCAU hash, skipping deadline and authorization checks on the idempotent path. - cancel(): _requirePayer returns silently (instead of reverting with RecurringCollectorAgreementNotFound) when no agreement or stored offer exists. Cancel against nothing is a no-op — same idempotent spirit. SubgraphService (IndexingAgreement library) - update(): defers state authority to the collector — _isValid replaces _isActive, and an activeTermsHash match short-circuits the SS-side event and terms re-write. - accept(): same-allocation re-accept is an idempotent no-op at the SS layer; different-allocation re-accept rebinds the agreement by clearing the old allocationToActiveAgreementId link and establishing the new one. Enables moving an active agreement to a new allocation when the original is closed. --- .../collectors/RecurringCollector.sol | 12 +- .../payments/recurring-collector/accept.t.sol | 16 +-- .../recurring-collector/acceptUnsigned.t.sol | 14 +-- .../recurring-collector/coverageGaps.t.sol | 8 +- .../payments/recurring-collector/update.t.sol | 33 ++++++ .../contracts/libraries/IndexingAgreement.sol | 90 ++++++++------- .../indexing-agreement/accept.t.sol | 105 ++++++++++++++++-- .../indexing-agreement/update.t.sol | 24 ++++ 8 files changed, 226 insertions(+), 76 deletions(-) diff --git a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol index 66c22b5b3..f93070681 100644 --- a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol +++ b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol @@ -228,6 +228,8 @@ contract RecurringCollector is _validateAndStoreAgreement(rca, agreementId, rcaHash); AgreementData storage agreement = _getStorage().agreements[agreementId]; + // Idempotent: already accepted → return silently + if (agreement.state == AgreementState.Accepted) return agreementId; require( agreement.state == AgreementState.NotAccepted, RecurringCollectorAgreementIncorrectState(agreementId, agreement.state) @@ -325,6 +327,12 @@ contract RecurringCollector is function update(RecurringCollectionAgreementUpdate calldata rcau, bytes calldata signature) external whenNotPaused { AgreementData storage agreement = _requireValidUpdateTarget(rcau.agreementId); + bytes32 rcauHash = _hashRCAU(rcau); + + // Idempotent: already at this version (state is Accepted per _requireValidUpdateTarget). + // Skip deadline + auth since no state change happens. + if (agreement.activeTermsHash == rcauHash) return; + /* solhint-disable gas-strict-inequalities */ require( rcau.deadline >= block.timestamp, @@ -332,8 +340,6 @@ contract RecurringCollector is ); /* solhint-enable gas-strict-inequalities */ - bytes32 rcauHash = _hashRCAU(rcau); - _requireAuthorization(agreement.payer, rcauHash, signature, rcau.agreementId, OFFER_TYPE_UPDATE); uint32 expectedNonce = agreement.updateNonce + 1; @@ -532,7 +538,7 @@ contract RecurringCollector is require(msg.sender == rca.payer, RecurringCollectorUnauthorizedCaller(msg.sender, rca.payer)); return; } - if (agreement.payer == address(0)) revert RecurringCollectorAgreementNotFound(agreementId); + if (agreement.payer == address(0)) return; revert RecurringCollectorUnauthorizedCaller(msg.sender, agreement.payer); } diff --git a/packages/horizon/test/unit/payments/recurring-collector/accept.t.sol b/packages/horizon/test/unit/payments/recurring-collector/accept.t.sol index d1742b690..6ff215170 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/accept.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/accept.t.sol @@ -25,6 +25,8 @@ contract RecurringCollectorAcceptTest is RecurringCollectorSharedTest { ) public { // Ensure non-empty signature so the signed path is taken (which checks deadline first) vm.assume(fuzzySignature.length > 0); + // Pranking as the proxy admin hits ProxyDeniedAdminAccess before the deadline check. + vm.assume(fuzzyRCA.dataService != _proxyAdmin); // Generate deterministic agreement ID for validation bytes16 agreementId = _recurringCollector.generateAgreementId( fuzzyRCA.payer, @@ -47,7 +49,7 @@ contract RecurringCollectorAcceptTest is RecurringCollectorSharedTest { _recurringCollector.accept(fuzzyRCA, fuzzySignature); } - function test_Accept_Revert_WhenAlreadyAccepted(FuzzyTestAccept calldata fuzzyTestAccept) public { + function test_Accept_Idempotent_WhenAlreadyAccepted(FuzzyTestAccept calldata fuzzyTestAccept) public { ( IRecurringCollector.RecurringCollectionAgreement memory acceptedRca, bytes memory signature, @@ -55,14 +57,12 @@ contract RecurringCollectorAcceptTest is RecurringCollectorSharedTest { bytes16 agreementId ) = _sensibleAuthorizeAndAccept(fuzzyTestAccept); - bytes memory expectedErr = abi.encodeWithSelector( - IRecurringCollector.RecurringCollectorAgreementIncorrectState.selector, - agreementId, - IRecurringCollector.AgreementState.Accepted - ); - vm.expectRevert(expectedErr); + // Re-accepting the same RCA is a no-op — succeeds without reverting or re-emitting. + vm.recordLogs(); vm.prank(acceptedRca.dataService); - _recurringCollector.accept(acceptedRca, signature); + bytes16 returnedId = _recurringCollector.accept(acceptedRca, signature); + assertEq(returnedId, agreementId); + assertEq(vm.getRecordedLogs().length, 0, "no event emitted on idempotent re-accept"); } /* solhint-enable graph/func-name-mixedcase */ diff --git a/packages/horizon/test/unit/payments/recurring-collector/acceptUnsigned.t.sol b/packages/horizon/test/unit/payments/recurring-collector/acceptUnsigned.t.sol index fb26e3d99..e535cd130 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/acceptUnsigned.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/acceptUnsigned.t.sol @@ -128,7 +128,7 @@ contract RecurringCollectorAcceptUnsignedTest is RecurringCollectorSharedTest { _recurringCollector.accept(rca, ""); } - function test_AcceptUnsigned_Revert_WhenAlreadyAccepted(FuzzyTestAccept calldata fuzzyTestAccept) public { + function test_AcceptUnsigned_Idempotent_WhenAlreadyAccepted(FuzzyTestAccept calldata fuzzyTestAccept) public { MockAgreementOwner approver = _newApprover(); IRecurringCollector.RecurringCollectionAgreement memory rca = _recurringCollectorHelper.sensibleRCA( fuzzyTestAccept.rca @@ -143,16 +143,10 @@ contract RecurringCollectorAcceptUnsignedTest is RecurringCollectorSharedTest { vm.prank(rca.dataService); bytes16 agreementId = _recurringCollector.accept(rca, ""); - // Stored offer persists, so authorization passes but state check fails - vm.expectRevert( - abi.encodeWithSelector( - IRecurringCollector.RecurringCollectorAgreementIncorrectState.selector, - agreementId, - IRecurringCollector.AgreementState.Accepted - ) - ); + // Re-accepting the same RCA is a no-op — succeeds without reverting. vm.prank(rca.dataService); - _recurringCollector.accept(rca, ""); + bytes16 returnedId = _recurringCollector.accept(rca, ""); + assertEq(returnedId, agreementId); } function test_AcceptUnsigned_Revert_WhenDeadlineElapsed() public { diff --git a/packages/horizon/test/unit/payments/recurring-collector/coverageGaps.t.sol b/packages/horizon/test/unit/payments/recurring-collector/coverageGaps.t.sol index 696f97584..6596d47e8 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/coverageGaps.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/coverageGaps.t.sol @@ -850,16 +850,14 @@ contract RecurringCollectorCoverageGapsTest is RecurringCollectorSharedTest { } // ══════════════════════════════════════════════════════════════════════ - // Gap 16 — _requirePayer: agreement not found (L528) + // Gap 16 — cancel: silent no-op when agreement not found // ══════════════════════════════════════════════════════════════════════ - function test_Cancel_Revert_WhenAgreementNotFound() public { + function test_Cancel_NoOp_WhenAgreementNotFound() public { bytes16 fakeId = bytes16(keccak256("nonexistent")); address caller = makeAddr("randomCaller"); - vm.expectRevert( - abi.encodeWithSelector(IRecurringCollector.RecurringCollectorAgreementNotFound.selector, fakeId) - ); + // Should not revert — nothing exists on-chain, so cancel is a no-op vm.prank(caller); _recurringCollector.cancel(fakeId, bytes32(0), SCOPE_ACTIVE); } diff --git a/packages/horizon/test/unit/payments/recurring-collector/update.t.sol b/packages/horizon/test/unit/payments/recurring-collector/update.t.sol index 57e8f0ad3..234e552d6 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/update.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/update.t.sol @@ -313,5 +313,38 @@ contract RecurringCollectorUpdateTest is RecurringCollectorSharedTest { assertEq(updatedAgreement2.updateNonce, 2); } + function test_Update_Idempotent_WhenAlreadyAtActiveHash(FuzzyTestUpdate calldata fuzzyTestUpdate) public { + ( + IRecurringCollector.RecurringCollectionAgreement memory acceptedRca, + , + uint256 signerKey, + bytes16 agreementId + ) = _sensibleAuthorizeAndAccept(fuzzyTestUpdate.fuzzyTestAccept); + + IRecurringCollector.RecurringCollectionAgreementUpdate memory rcau = _recurringCollectorHelper.sensibleRCAU( + fuzzyTestUpdate.rcau + ); + rcau.agreementId = agreementId; + rcau.nonce = 1; + (, bytes memory signature) = _recurringCollectorHelper.generateSignedRCAU(rcau, signerKey); + + // First update consumes nonce 1 and sets activeTermsHash = hash(rcau). + vm.prank(acceptedRca.dataService); + _recurringCollector.update(rcau, signature); + + IRecurringCollector.AgreementData memory afterFirst = _recurringCollector.getAgreement(agreementId); + assertEq(afterFirst.updateNonce, 1, "nonce advanced to 1 after first update"); + + // Re-submitting the same RCAU is a no-op — nonce does NOT advance, no event, no revert. + vm.recordLogs(); + vm.prank(acceptedRca.dataService); + _recurringCollector.update(rcau, signature); + assertEq(vm.getRecordedLogs().length, 0, "no event emitted on idempotent re-update"); + + IRecurringCollector.AgreementData memory afterSecond = _recurringCollector.getAgreement(agreementId); + assertEq(afterSecond.updateNonce, 1, "nonce unchanged on idempotent re-update"); + assertEq(afterSecond.activeTermsHash, afterFirst.activeTermsHash, "activeTermsHash unchanged"); + } + /* solhint-enable graph/func-name-mixedcase */ } diff --git a/packages/subgraph-service/contracts/libraries/IndexingAgreement.sol b/packages/subgraph-service/contracts/libraries/IndexingAgreement.sol index 347eed37e..8516334f4 100644 --- a/packages/subgraph-service/contracts/libraries/IndexingAgreement.sol +++ b/packages/subgraph-service/contracts/libraries/IndexingAgreement.sol @@ -218,12 +218,6 @@ library IndexingAgreement { bytes32 allocationDeploymentId ); - /** - * @notice Thrown when the agreement is already accepted - * @param agreementId The agreement ID - */ - error IndexingAgreementAlreadyAccepted(bytes16 agreementId); - /** * @notice Thrown when an allocation already has an active agreement * @param allocationId The allocation ID @@ -310,42 +304,48 @@ library IndexingAgreement { IIndexingAgreement.State storage agreement = self.agreements[agreementId]; - require(agreement.allocationId == address(0), IndexingAgreementAlreadyAccepted(agreementId)); - - require( - allocation.subgraphDeploymentId == metadata.subgraphDeploymentId, - IndexingAgreementDeploymentIdMismatch( - metadata.subgraphDeploymentId, + // Accept is idempotent for the same allocation, and supports moving + // the agreement to a different allocation. The collector's accept handles state + // validity (reverts if the agreement is cancelled, no-ops if already accepted). + if (agreement.allocationId != allocationId) { + require( + allocation.subgraphDeploymentId == metadata.subgraphDeploymentId, + IndexingAgreementDeploymentIdMismatch( + metadata.subgraphDeploymentId, + allocationId, + allocation.subgraphDeploymentId + ) + ); + + // Ensure that an allocation can only have one active indexing agreement + require( + self.allocationToActiveAgreementId[allocationId] == bytes16(0), + AllocationAlreadyHasIndexingAgreement(allocationId) + ); + + if (agreement.allocationId != address(0)) delete self.allocationToActiveAgreementId[agreement.allocationId]; + agreement.allocationId = allocationId; + + self.allocationToActiveAgreementId[allocationId] = agreementId; + + agreement.version = metadata.version; + + require( + metadata.version == IIndexingAgreement.IndexingAgreementVersion.V1, + IndexingAgreementInvalidVersion(metadata.version) + ); + _setTermsV1(self, agreementId, metadata.terms, rca.maxOngoingTokensPerSecond); + + emit IndexingAgreementAccepted( + rca.serviceProvider, + rca.payer, + agreementId, allocationId, - allocation.subgraphDeploymentId - ) - ); - - // Ensure that an allocation can only have one active indexing agreement - require( - self.allocationToActiveAgreementId[allocationId] == bytes16(0), - AllocationAlreadyHasIndexingAgreement(allocationId) - ); - self.allocationToActiveAgreementId[allocationId] = agreementId; - - agreement.version = metadata.version; - agreement.allocationId = allocationId; - - require( - metadata.version == IIndexingAgreement.IndexingAgreementVersion.V1, - IndexingAgreementInvalidVersion(metadata.version) - ); - _setTermsV1(self, agreementId, metadata.terms, rca.maxOngoingTokensPerSecond); - - emit IndexingAgreementAccepted( - rca.serviceProvider, - rca.payer, - agreementId, - allocationId, - metadata.subgraphDeploymentId, - metadata.version, - metadata.terms - ); + metadata.subgraphDeploymentId, + metadata.version, + metadata.terms + ); + } require( _directory().recurringCollector().accept(rca, authData) == agreementId, @@ -380,12 +380,18 @@ library IndexingAgreement { bytes calldata authData ) external { IIndexingAgreement.AgreementWrapper memory wrapper = _get(self, rcau.agreementId); - require(_isActive(wrapper), IndexingAgreementNotActive(rcau.agreementId)); + // SS gate: only checks that this is an SS-managed, tracked agreement. Collector is the + // state authority — it reverts if the agreement cannot actually accept an update. + require(_isValid(wrapper), IndexingAgreementNotActive(rcau.agreementId)); require( wrapper.collectorAgreement.serviceProvider == indexer, IndexingAgreementNotAuthorized(rcau.agreementId, indexer) ); + // Idempotent: this RCAU is already the active version — both SS terms and collector state + // are in sync because both are written together on the original update. + if (wrapper.collectorAgreement.activeTermsHash == _directory().recurringCollector().hashRCAU(rcau)) return; + UpdateIndexingAgreementMetadata memory metadata = IndexingAgreementDecoder.decodeRCAUMetadata(rcau.metadata); require( diff --git a/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/accept.t.sol b/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/accept.t.sol index 1d2e2b9fb..b2853949d 100644 --- a/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/accept.t.sol +++ b/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/accept.t.sol @@ -5,6 +5,7 @@ import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/P import { ProvisionManager } from "@graphprotocol/horizon/contracts/data-service/utilities/ProvisionManager.sol"; import { IRecurringCollector } from "@graphprotocol/interfaces/contracts/horizon/IRecurringCollector.sol"; import { IAllocation } from "@graphprotocol/interfaces/contracts/subgraph-service/internal/IAllocation.sol"; +import { IIndexingAgreement } from "@graphprotocol/interfaces/contracts/subgraph-service/internal/IIndexingAgreement.sol"; import { IndexingAgreement } from "../../../../contracts/libraries/IndexingAgreement.sol"; import { IndexingAgreementDecoder } from "../../../../contracts/libraries/IndexingAgreementDecoder.sol"; @@ -231,7 +232,9 @@ contract SubgraphServiceIndexingAgreementAcceptTest is SubgraphServiceIndexingAg subgraphService.acceptIndexingAgreement(indexerState.allocationId, unacceptableRca, signature); } - function test_SubgraphService_AcceptIndexingAgreement_Revert_WhenAgreementAlreadyAccepted(Seed memory seed) public { + function test_SubgraphService_AcceptIndexingAgreement_Idempotent_WhenAlreadyAcceptedSameAllocation( + Seed memory seed + ) public { Context storage ctx = _newCtx(seed); IndexerState memory indexerState = _withIndexer(ctx); ( @@ -239,19 +242,20 @@ contract SubgraphServiceIndexingAgreementAcceptTest is SubgraphServiceIndexingAg bytes16 agreementId ) = _withAcceptedIndexingAgreement(ctx, indexerState); - // Re-sign for the re-accept attempt (the original signature was consumed) + // Re-sign for the re-accept (the original signature was consumed) (, bytes memory signature) = _recurringCollectorHelper.generateSignedRCA( acceptedRca, ctx.payer.signerPrivateKey ); - bytes memory expectedErr = abi.encodeWithSelector( - IndexingAgreement.IndexingAgreementAlreadyAccepted.selector, - agreementId - ); - vm.expectRevert(expectedErr); + // Re-accepting the same RCA on the same allocation is a no-op. resetPrank(ctx.indexers[0].addr); - subgraphService.acceptIndexingAgreement(ctx.indexers[0].allocationId, acceptedRca, signature); + bytes16 returnedId = subgraphService.acceptIndexingAgreement( + ctx.indexers[0].allocationId, + acceptedRca, + signature + ); + assertEq(returnedId, agreementId); } function test_SubgraphService_AcceptIndexingAgreement_Revert_WhenAgreementAlreadyAllocated( @@ -384,5 +388,90 @@ contract SubgraphServiceIndexingAgreementAcceptTest is SubgraphServiceIndexingAg resetPrank(indexerState.addr); subgraphService.acceptIndexingAgreement(indexerState.allocationId, acceptableRca, signature); } + + function test_SubgraphService_AcceptIndexingAgreement_Rebinds_WhenDifferentAllocation( + Seed memory seed, + uint256 secondAllocationKey + ) public { + Context storage ctx = _newCtx(seed); + IndexerState memory indexerState = _withIndexer(ctx); + ( + IRecurringCollector.RecurringCollectionAgreement memory acceptedRca, + bytes16 agreementId + ) = _withAcceptedIndexingAgreement(ctx, indexerState); + + // Agreement is now bound to the first allocation. + IIndexingAgreement.AgreementWrapper memory before = subgraphService.getIndexingAgreement(agreementId); + assertEq(before.agreement.allocationId, indexerState.allocationId, "starts bound to first allocation"); + + // Derive a second allocation for the same indexer + same subgraph deployment. The first + // allocation already consumed the indexer's provision, so top up first. + uint256 extraTokens = 10_000_000 ether; + deal({ token: address(token), to: indexerState.addr, give: extraTokens }); + resetPrank(indexerState.addr); + _addToProvision(indexerState.addr, extraTokens); + + secondAllocationKey = boundKey(secondAllocationKey); + address secondAllocationId = vm.addr(secondAllocationKey); + vm.assume(secondAllocationId != indexerState.allocationId); + vm.assume(ctx.allocations[secondAllocationId] == address(0)); + ctx.allocations[secondAllocationId] = indexerState.addr; + + bytes memory allocData = _createSubgraphAllocationData( + indexerState.addr, + indexerState.subgraphDeploymentId, + secondAllocationKey, + extraTokens + ); + _startService(indexerState.addr, allocData); + + // Re-sign the same RCA (original signature was consumed on first accept). + (, bytes memory signature) = _recurringCollectorHelper.generateSignedRCA( + acceptedRca, + ctx.payer.signerPrivateKey + ); + + // Re-accepting the same agreement on the new allocation rebinds it: + // event is re-emitted, agreement.allocationId updates, old allocation's active-agreement + // mapping is cleared. Collector's accept() is a no-op (already Accepted). + IndexingAgreement.AcceptIndexingAgreementMetadata memory metadata = abi.decode( + acceptedRca.metadata, + (IndexingAgreement.AcceptIndexingAgreementMetadata) + ); + vm.expectEmit(address(subgraphService)); + emit IndexingAgreement.IndexingAgreementAccepted( + acceptedRca.serviceProvider, + acceptedRca.payer, + agreementId, + secondAllocationId, + metadata.subgraphDeploymentId, + metadata.version, + metadata.terms + ); + resetPrank(indexerState.addr); + bytes16 returnedId = subgraphService.acceptIndexingAgreement(secondAllocationId, acceptedRca, signature); + assertEq(returnedId, agreementId, "rebind returns same agreementId"); + + IIndexingAgreement.AgreementWrapper memory rebound = subgraphService.getIndexingAgreement(agreementId); + assertEq(rebound.agreement.allocationId, secondAllocationId, "rebound to second allocation"); + assertEq( + uint8(rebound.collectorAgreement.state), + uint8(IRecurringCollector.AgreementState.Accepted), + "collector state still Accepted after rebind" + ); + + // Closing the OLD allocation must not cancel the agreement — the agreement no longer + // points to it. onCloseAllocation's allocationToActiveAgreementId lookup should return 0. + resetPrank(indexerState.addr); + subgraphService.stopService(indexerState.addr, abi.encode(indexerState.allocationId)); + + IIndexingAgreement.AgreementWrapper memory afterOldClose = subgraphService.getIndexingAgreement(agreementId); + assertEq( + uint8(afterOldClose.collectorAgreement.state), + uint8(IRecurringCollector.AgreementState.Accepted), + "closing old allocation leaves agreement intact" + ); + assertEq(afterOldClose.agreement.allocationId, secondAllocationId, "still bound to second allocation"); + } /* solhint-enable graph/func-name-mixedcase */ } diff --git a/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/update.t.sol b/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/update.t.sol index 9f1abc180..dcd6bf32f 100644 --- a/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/update.t.sol +++ b/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/update.t.sol @@ -227,5 +227,29 @@ contract SubgraphServiceIndexingAgreementUpgradeTest is SubgraphServiceIndexingA resetPrank(indexerState.addr); subgraphService.updateIndexingAgreement(indexerState.addr, acceptableRcau, authData); } + + function test_SubgraphService_UpdateIndexingAgreement_Idempotent_WhenAlreadyAtActiveHash(Seed memory seed) public { + Context storage ctx = _newCtx(seed); + IndexerState memory indexerState = _withIndexer(ctx); + (IRecurringCollector.RecurringCollectionAgreement memory acceptedRca, ) = _withAcceptedIndexingAgreement( + ctx, + indexerState + ); + ( + IRecurringCollector.RecurringCollectionAgreementUpdate memory acceptableRcau, + bytes memory authData + ) = _generateAcceptableSignedRCAU(ctx, acceptedRca); + + // First update sets activeTermsHash = hash(rcau) on the collector and applies SS terms. + resetPrank(indexerState.addr); + subgraphService.updateIndexingAgreement(indexerState.addr, acceptableRcau, authData); + + // Re-submitting the same RCAU is a no-op at the SS layer: + // the hash match short-circuits before re-emitting or re-writing terms. + vm.recordLogs(); + resetPrank(indexerState.addr); + subgraphService.updateIndexingAgreement(indexerState.addr, acceptableRcau, authData); + assertEq(vm.getRecordedLogs().length, 0, "no event emitted on idempotent re-update"); + } /* solhint-enable graph/func-name-mixedcase */ } From 058bdb7b35371526321110f3818fdcdde253b270 Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:58:55 +0000 Subject: [PATCH 25/31] refactor(collector): hoist payer check from _offerNew/_offerUpdate into offer() Move the require(msg.sender == payer) from both _offerNew and _offerUpdate into offer() as a post-check on details.payer. Single site for the payer authorization requirement. --- .../contracts/payments/collectors/RecurringCollector.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol index f93070681..0eab6a626 100644 --- a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol +++ b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol @@ -429,6 +429,8 @@ contract RecurringCollector is if (offerType == OFFER_TYPE_NEW) details = _offerNew(data); else if (offerType == OFFER_TYPE_UPDATE) details = _offerUpdate(data); else revert RecurringCollectorInvalidCollectData(data); + + require(msg.sender == details.payer, RecurringCollectorUnauthorizedCaller(msg.sender, details.payer)); } /** @@ -439,7 +441,6 @@ contract RecurringCollector is function _offerNew(bytes calldata _data) private returns (AgreementDetails memory details) { RecurringCollectorStorage storage $ = _getStorage(); RecurringCollectionAgreement memory rca = abi.decode(_data, (RecurringCollectionAgreement)); - require(msg.sender == rca.payer, RecurringCollectorUnauthorizedCaller(msg.sender, rca.payer)); _requirePayerToSupportEligibilityCheck(rca.payer, rca.conditions); (bytes16 agreementId, bytes32 rcaHash) = _rcaIdAndHash(rca); @@ -486,7 +487,6 @@ contract RecurringCollector is details.dataService = agreement.dataService; details.serviceProvider = agreement.serviceProvider; } - require(msg.sender == payer, RecurringCollectorUnauthorizedCaller(msg.sender, payer)); _requirePayerToSupportEligibilityCheck(payer, rcau.conditions); bytes32 offerHash = _hashRCAU(rcau); From b869e258117437efe0278425110c037ab0cfb3ee Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:17:31 +0000 Subject: [PATCH 26/31] refactor(collector): cleanup and reject expired offers at offer() time Preparatory cleanup ahead of the hash-keyed storage restructure: - Hoist `solhint-disable gas-strict-inequalities` to file level; drop per-block/per-line fences and flip `deadline >= block.timestamp` callsites to the idiomatic `block.timestamp <= deadline`. - Reject expired offers at `offer()` time in `_offerNew`/`_offerUpdate` rather than deferring to `accept`/`update`. Previously an expired offer would be stored and only fail at acceptance. No storage or interface changes. --- .../collectors/RecurringCollector.sol | 24 +++++++++---------- .../recurring-collector/updateUnsigned.t.sol | 9 +++---- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol index 0eab6a626..02819d638 100644 --- a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol +++ b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.27; +// solhint-disable gas-strict-inequalities + import { EIP712Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol"; import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; @@ -212,12 +214,10 @@ contract RecurringCollector is RecurringCollectionAgreement calldata rca, bytes calldata signature ) external whenNotPaused returns (bytes16 agreementId) { - /* solhint-disable gas-strict-inequalities */ require( - rca.deadline >= block.timestamp, + block.timestamp <= rca.deadline, RecurringCollectorAgreementDeadlineElapsed(block.timestamp, rca.deadline) ); - /* solhint-enable gas-strict-inequalities */ require(msg.sender == rca.dataService, RecurringCollectorUnauthorizedCaller(msg.sender, rca.dataService)); bytes32 rcaHash; @@ -333,12 +333,10 @@ contract RecurringCollector is // Skip deadline + auth since no state change happens. if (agreement.activeTermsHash == rcauHash) return; - /* solhint-disable gas-strict-inequalities */ require( - rcau.deadline >= block.timestamp, + block.timestamp <= rcau.deadline, RecurringCollectorAgreementDeadlineElapsed(block.timestamp, rcau.deadline) ); - /* solhint-enable gas-strict-inequalities */ _requireAuthorization(agreement.payer, rcauHash, signature, rcau.agreementId, OFFER_TYPE_UPDATE); @@ -441,6 +439,10 @@ contract RecurringCollector is function _offerNew(bytes calldata _data) private returns (AgreementDetails memory details) { RecurringCollectorStorage storage $ = _getStorage(); RecurringCollectionAgreement memory rca = abi.decode(_data, (RecurringCollectionAgreement)); + require( + block.timestamp <= rca.deadline, + RecurringCollectorAgreementDeadlineElapsed(block.timestamp, rca.deadline) + ); _requirePayerToSupportEligibilityCheck(rca.payer, rca.conditions); (bytes16 agreementId, bytes32 rcaHash) = _rcaIdAndHash(rca); @@ -465,6 +467,10 @@ contract RecurringCollector is function _offerUpdate(bytes calldata _data) private returns (AgreementDetails memory details) { RecurringCollectorStorage storage $ = _getStorage(); RecurringCollectionAgreementUpdate memory rcau = abi.decode(_data, (RecurringCollectionAgreementUpdate)); + require( + block.timestamp <= rcau.deadline, + RecurringCollectorAgreementDeadlineElapsed(block.timestamp, rcau.deadline) + ); bytes16 agreementId = rcau.agreementId; // Payer check: look up the existing agreement or the stored RCA offer @@ -663,12 +669,10 @@ contract RecurringCollector is if (_params.tokens != 0) { uint256 slippage = _params.tokens - tokensToCollect; - /* solhint-disable gas-strict-inequalities */ require( slippage <= _params.maxSlippage, RecurringCollectorExcessiveSlippage(_params.tokens, tokensToCollect, _params.maxSlippage) ); - /* solhint-enable gas-strict-inequalities */ } agreement.lastCollectionAt = uint64(block.timestamp); @@ -821,7 +825,6 @@ contract RecurringCollector is // Collection window needs to be at least MIN_SECONDS_COLLECTION_WINDOW require( _maxSecondsPerCollection > _minSecondsPerCollection && - // solhint-disable-next-line gas-strict-inequalities (_maxSecondsPerCollection - _minSecondsPerCollection >= MIN_SECONDS_COLLECTION_WINDOW), RecurringCollectorAgreementInvalidCollectionWindow( MIN_SECONDS_COLLECTION_WINDOW, @@ -832,7 +835,6 @@ contract RecurringCollector is // Agreement needs to last at least one min collection window require( - // solhint-disable-next-line gas-strict-inequalities _endsAt - block.timestamp >= _minSecondsPerCollection + MIN_SECONDS_COLLECTION_WINDOW, RecurringCollectorAgreementInvalidDuration( _minSecondsPerCollection + MIN_SECONDS_COLLECTION_WINDOW, @@ -886,7 +888,6 @@ contract RecurringCollector is block.timestamp > _agreement.endsAt; if (!canceledOrElapsed) { require( - // solhint-disable-next-line gas-strict-inequalities _collectionSeconds >= _agreement.minSecondsPerCollection, RecurringCollectorCollectionTooSoon( _agreementId, @@ -1264,7 +1265,6 @@ contract RecurringCollector is uint256 maxOngoingTokensPerSecond, uint256 maxInitialTokens ) private pure returns (uint256) { - // solhint-disable-next-line gas-strict-inequalities if (windowEnd <= windowStart) return 0; uint256 windowSeconds = windowEnd - windowStart; uint256 effectiveSeconds = windowSeconds < maxSecondsPerCollection ? windowSeconds : maxSecondsPerCollection; diff --git a/packages/horizon/test/unit/payments/recurring-collector/updateUnsigned.t.sol b/packages/horizon/test/unit/payments/recurring-collector/updateUnsigned.t.sol index 84eab9b75..b0059e303 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/updateUnsigned.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/updateUnsigned.t.sol @@ -217,20 +217,17 @@ contract RecurringCollectorUpdateUnsignedTest is RecurringCollectorSharedTest { IRecurringCollector.RecurringCollectionAgreementUpdate memory rcau = _makeSimpleRCAU(agreementId, 1); - // Set the update deadline in the past + // Set the update deadline in the past — offer() now rejects expired deadlines rcau.deadline = uint64(block.timestamp - 1); - vm.prank(address(approver)); - _recurringCollector.offer(OFFER_TYPE_UPDATE, abi.encode(rcau), 0); - bytes memory expectedErr = abi.encodeWithSelector( IRecurringCollector.RecurringCollectorAgreementDeadlineElapsed.selector, block.timestamp, rcau.deadline ); vm.expectRevert(expectedErr); - vm.prank(rca.dataService); - _recurringCollector.update(rcau, ""); + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_UPDATE, abi.encode(rcau), 0); } /* solhint-enable graph/func-name-mixedcase */ From c078589d1b6e7d42127b9165b9afc9dc2f43d771 Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Wed, 22 Apr 2026 14:50:51 +0000 Subject: [PATCH 27/31] fix(collector): validate offer terms against deadline, not block.timestamp Collection-window and duration checks now use the offer's acceptance deadline as the reference point instead of `block.timestamp`, making validation time-independent: if terms pass here they remain valid for any acceptance on or before `deadline`. Callers still enforce `block.timestamp <= deadline` at the acceptance entry point. - `_requireValidCollectionWindowParams` takes a `_deadline` parameter and becomes `pure`. `_endsAt > block.timestamp` becomes `_deadline < _endsAt`; `_endsAt - block.timestamp >= min + WINDOW` becomes `min + WINDOW <= _endsAt - _deadline`. - `_requireValidTerms` propagates `_deadline` to the window check. - Accept/update call sites pass the RCA/RCAU deadline. - Interface: replace `RecurringCollectorAgreementElapsedEndsAt` with `RecurringCollectorAgreementEndsBeforeDeadline(deadline, endsAt)`. Prerequisite for hash-keyed terms storage, where a single stored hash must remain validatable without re-checking against wall clock on every read. --- .../collectors/RecurringCollector.sol | 30 ++++++++++++------- .../acceptValidation.t.sol | 16 +++++----- .../contracts/horizon/IRecurringCollector.sol | 6 ++-- 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol index 02819d638..92f366075 100644 --- a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol +++ b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol @@ -274,7 +274,7 @@ contract RecurringCollector is ); _requireValidTerms( - _rca.endsAt, _rca.minSecondsPerCollection, _rca.maxSecondsPerCollection, + _rca.deadline, _rca.endsAt, _rca.minSecondsPerCollection, _rca.maxSecondsPerCollection, _rca.payer, _rca.conditions, _rca.maxOngoingTokensPerSecond ); @@ -809,18 +809,23 @@ contract RecurringCollector is /** * @notice Requires that the collection window parameters are valid. - * + * @dev Validated against `_deadline` (the offer's acceptance deadline) rather than + * `block.timestamp`, making this check time-independent: if terms pass here they remain + * valid for any acceptance that happens on or before `_deadline`. Callers must enforce + * `block.timestamp <= _deadline` at the acceptance entry point. + * @param _deadline The offer's acceptance deadline * @param _endsAt The end time of the agreement * @param _minSecondsPerCollection The minimum seconds per collection * @param _maxSecondsPerCollection The maximum seconds per collection */ function _requireValidCollectionWindowParams( + uint64 _deadline, uint64 _endsAt, uint32 _minSecondsPerCollection, uint32 _maxSecondsPerCollection - ) private view { - // Agreement needs to end in the future - require(_endsAt > block.timestamp, RecurringCollectorAgreementElapsedEndsAt(block.timestamp, _endsAt)); + ) private pure { + // Agreement must end after the deadline + require(_deadline < _endsAt, RecurringCollectorAgreementEndsBeforeDeadline(_deadline, _endsAt)); // Collection window needs to be at least MIN_SECONDS_COLLECTION_WINDOW require( @@ -833,19 +838,21 @@ contract RecurringCollector is ) ); - // Agreement needs to last at least one min collection window + // Even if accepted at the deadline at least one min collection window must remain require( - _endsAt - block.timestamp >= _minSecondsPerCollection + MIN_SECONDS_COLLECTION_WINDOW, + _minSecondsPerCollection + MIN_SECONDS_COLLECTION_WINDOW <= _endsAt - _deadline, RecurringCollectorAgreementInvalidDuration( _minSecondsPerCollection + MIN_SECONDS_COLLECTION_WINDOW, - _endsAt - block.timestamp + _endsAt - _deadline ) ); } /** * @notice Validates offer terms: collection window, eligibility support, and overflow. - * @dev Called by _validateAndStoreAgreement and _validateAndStoreUpdate. + * @dev Called by _validateAndStoreAgreement and _validateAndStoreUpdate. Time-independent — + * validates against the offer's deadline so the check is stable across the offer's lifetime. + * @param _deadline The offer's acceptance deadline * @param _endsAt The end time of the agreement * @param _minSecondsPerCollection The minimum seconds per collection * @param _maxSecondsPerCollection The maximum seconds per collection @@ -854,6 +861,7 @@ contract RecurringCollector is * @param _maxOngoingTokensPerSecond The maximum ongoing tokens per second */ function _requireValidTerms( + uint64 _deadline, uint64 _endsAt, uint32 _minSecondsPerCollection, uint32 _maxSecondsPerCollection, @@ -861,7 +869,7 @@ contract RecurringCollector is uint16 _conditions, uint256 _maxOngoingTokensPerSecond ) private view { - _requireValidCollectionWindowParams(_endsAt, _minSecondsPerCollection, _maxSecondsPerCollection); + _requireValidCollectionWindowParams(_deadline, _endsAt, _minSecondsPerCollection, _maxSecondsPerCollection); _requirePayerToSupportEligibilityCheck(_payer, _conditions); // Reverts on overflow — rejecting excessive terms that could prevent collection _maxOngoingTokensPerSecond * _maxSecondsPerCollection * 1024; @@ -1058,7 +1066,7 @@ contract RecurringCollector is RecurringCollectorStorage storage $ = _getStorage(); _requireValidTerms( - _rcau.endsAt, _rcau.minSecondsPerCollection, _rcau.maxSecondsPerCollection, + _rcau.deadline, _rcau.endsAt, _rcau.minSecondsPerCollection, _rcau.maxSecondsPerCollection, _agreement.payer, _rcau.conditions, _rcau.maxOngoingTokensPerSecond ); diff --git a/packages/horizon/test/unit/payments/recurring-collector/acceptValidation.t.sol b/packages/horizon/test/unit/payments/recurring-collector/acceptValidation.t.sol index 5e47e2fb4..91e3e0bdd 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/acceptValidation.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/acceptValidation.t.sol @@ -69,11 +69,11 @@ contract RecurringCollectorAcceptValidationTest is RecurringCollectorSharedTest _recurringCollector.accept(rca, signature); } - // ==================== endsAt validation (L545) ==================== + // ==================== endsAt validation ==================== - function test_Accept_Revert_WhenEndsAtInPast() public { + function test_Accept_Revert_WhenEndsAtNotAfterDeadline() public { IRecurringCollector.RecurringCollectionAgreement memory rca = _makeValidRCA(); - rca.endsAt = uint64(block.timestamp); // endsAt == now, fails "endsAt > block.timestamp" + rca.endsAt = rca.deadline; // endsAt == deadline, fails "endsAt > deadline" _recurringCollectorHelper.authorizeSignerWithChecks(rca.payer, SIGNER_KEY); (, bytes memory signature) = _recurringCollectorHelper.generateSignedRCA(rca, SIGNER_KEY); @@ -81,8 +81,8 @@ contract RecurringCollectorAcceptValidationTest is RecurringCollectorSharedTest vm.expectRevert( abi.encodeWithSelector( - IRecurringCollector.RecurringCollectorAgreementElapsedEndsAt.selector, - block.timestamp, + IRecurringCollector.RecurringCollectorAgreementEndsBeforeDeadline.selector, + rca.deadline, rca.endsAt ) ); @@ -142,12 +142,12 @@ contract RecurringCollectorAcceptValidationTest is RecurringCollectorSharedTest function test_Accept_Revert_WhenDurationTooShort() public { IRecurringCollector.RecurringCollectionAgreement memory rca = _makeValidRCA(); - // Need: endsAt - now >= minSecondsPerCollection + MIN_SECONDS_COLLECTION_WINDOW + // Need: endsAt - deadline >= minSecondsPerCollection + MIN_SECONDS_COLLECTION_WINDOW // Set duration just under the minimum uint32 minWindow = _recurringCollector.MIN_SECONDS_COLLECTION_WINDOW(); rca.minSecondsPerCollection = 600; rca.maxSecondsPerCollection = 600 + minWindow; // valid window - rca.endsAt = uint64(block.timestamp + rca.minSecondsPerCollection + minWindow - 1); // 1 second too short + rca.endsAt = rca.deadline + rca.minSecondsPerCollection + minWindow - 1; // 1 second too short _recurringCollectorHelper.authorizeSignerWithChecks(rca.payer, SIGNER_KEY); (, bytes memory signature) = _recurringCollectorHelper.generateSignedRCA(rca, SIGNER_KEY); @@ -157,7 +157,7 @@ contract RecurringCollectorAcceptValidationTest is RecurringCollectorSharedTest abi.encodeWithSelector( IRecurringCollector.RecurringCollectorAgreementInvalidDuration.selector, rca.minSecondsPerCollection + minWindow, - rca.endsAt - block.timestamp + uint256(rca.endsAt - rca.deadline) ) ); vm.prank(rca.dataService); diff --git a/packages/interfaces/contracts/horizon/IRecurringCollector.sol b/packages/interfaces/contracts/horizon/IRecurringCollector.sol index 6315033e2..74ccde753 100644 --- a/packages/interfaces/contracts/horizon/IRecurringCollector.sol +++ b/packages/interfaces/contracts/horizon/IRecurringCollector.sol @@ -316,11 +316,11 @@ interface IRecurringCollector is IAuthorizable, IAgreementCollector { error RecurringCollectorAgreementAddressNotSet(); /** - * @notice Thrown when accepting or upgrading an agreement with an elapsed endsAt - * @param currentTimestamp The current timestamp + * @notice Thrown when an agreement's endsAt is not strictly after its acceptance deadline. + * @param deadline The offer acceptance deadline * @param endsAt The agreement end timestamp */ - error RecurringCollectorAgreementElapsedEndsAt(uint256 currentTimestamp, uint64 endsAt); + error RecurringCollectorAgreementEndsBeforeDeadline(uint64 deadline, uint64 endsAt); /** * @notice Thrown when accepting or upgrading an agreement with an elapsed endsAt From 6cd47757ee7297e9c940e77e8993b79d4afce414 Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Wed, 22 Apr 2026 14:51:39 +0000 Subject: [PATCH 28/31] feat(collector): hash-keyed decoded terms with version-indexed views (TRST-L-11) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructure agreement storage around decoded terms keyed by EIP-712 hash. All internal reads access terms[hash] uniformly — no abi.decode, no offerType discrimination in read paths. Storage changes: - rcaOffers/rcauOffers → terms[hash] (single mapping, decoded fields) - AgreementData slimmed to identity + lifecycle + hash pointers (5 slots, was 7). Terms fields removed; getAgreement() returns the slim struct. - AgreementData.pendingTermsHash added for queued RCAU offers. Architecture: - _storeTerms: validates + stores + links (active or pending). Single write gate — every stored term is validated (invariant). - _termsFromRCA/_termsFromRCAU: type → AgreementTerms extraction. - _registerNew: composite for NEW path (register identity + store + link). Shared by offer(NEW) and accept(). Idempotent. - _activeClaimWindow + _maxClaimForTerms: replace 3-way state dispatch in max-claim calculation. Interface additions: - OfferCancelled event for SCOPE_PENDING cancellations. - AgreementData.pendingTermsHash. --- .../collectors/RecurringCollector.sol | 578 +++++++++--------- .../payments/recurring-collector/accept.t.sol | 167 +++++ .../recurring-collector/coverageGaps.t.sol | 73 +++ .../recurring-collector/getMaxNextClaim.t.sol | 184 ++++++ .../recurring-collector/hashRoundTrip.t.sol | 4 +- .../recurring-collector/mixedPath.t.sol | 83 ++- .../offerStorageLifecycle.t.sol | 35 +- .../payments/recurring-collector/update.t.sol | 53 +- .../recurring-collector/updateUnsigned.t.sol | 6 +- .../recurring-collector/upgradeScenario.t.sol | 2 +- .../recurring-collector/viewFunctions.t.sol | 17 +- .../contracts/horizon/IRecurringCollector.sol | 61 +- packages/issuance/audits/PR1301/TRST-L-7.md | 11 + .../indexing-agreement/accept.t.sol | 58 ++ .../AgreementLifecycleAdvanced.t.sol | 82 +++ 15 files changed, 1039 insertions(+), 375 deletions(-) diff --git a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol index 92f366075..cfb6dbb93 100644 --- a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol +++ b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol @@ -23,6 +23,8 @@ import { ACCEPTED, REGISTERED, UPDATE, + VERSION_CURRENT, + VERSION_NEXT, SCOPE_ACTIVE, SCOPE_PENDING } from "@graphprotocol/interfaces/contracts/horizon/IAgreementCollector.sol"; @@ -86,10 +88,18 @@ contract RecurringCollector is ); /* solhint-enable gas-small-strings */ - /// @notice A stored offer (RCA or RCAU) with its EIP-712 hash - struct StoredOffer { - bytes32 offerHash; - bytes data; + /// @notice Decoded agreement terms keyed by EIP-712 hash. `data` preserves the + /// original ABI-encoded RCA or RCAU for {getAgreementOfferAt} consumers. + struct AgreementTerms { + uint8 offerType; // 1 byte ─┐ slot 0 (27/32) + uint64 endsAt; // 8 bytes ─┤ + uint64 deadline; // 8 bytes ─┤ + uint32 minSecondsPerCollection; // 4 bytes ─┤ + uint32 maxSecondsPerCollection; // 4 bytes ─┤ + uint16 conditions; // 2 bytes ─┘ + uint256 maxInitialTokens; // 32 bytes ── slot 1 + uint256 maxOngoingTokensPerSecond; // 32 bytes ── slot 2 + bytes data; // ── slot 3 (pointer) } /// @custom:storage-location erc7201:graphprotocol.storage.RecurringCollector @@ -98,10 +108,9 @@ contract RecurringCollector is mapping(address pauseGuardian => bool allowed) pauseGuardians; /// @notice Tracks agreements mapping(bytes16 agreementId => AgreementData data) agreements; - /// @notice Stored RCA offers (pre-approval), keyed by agreement ID - mapping(bytes16 agreementId => StoredOffer offer) rcaOffers; - /// @notice Stored RCAU offers (pre-approval), keyed by agreement ID - mapping(bytes16 agreementId => StoredOffer offer) rcauOffers; + /// @notice Decoded agreement terms, keyed by EIP-712 hash. + /// Referenced by AgreementData.activeTermsHash and pendingTermsHash. + mapping(bytes32 termsHash => AgreementTerms terms) terms; } /// @dev keccak256(abi.encode(uint256(keccak256("graphprotocol.storage.RecurringCollector")) - 1)) & ~bytes32(uint256(0xff)) @@ -214,22 +223,26 @@ contract RecurringCollector is RecurringCollectionAgreement calldata rca, bytes calldata signature ) external whenNotPaused returns (bytes16 agreementId) { - require( - block.timestamp <= rca.deadline, - RecurringCollectorAgreementDeadlineElapsed(block.timestamp, rca.deadline) - ); require(msg.sender == rca.dataService, RecurringCollectorUnauthorizedCaller(msg.sender, rca.dataService)); bytes32 rcaHash; (agreementId, rcaHash) = _rcaIdAndHash(rca); + AgreementData storage agreement = _getStorage().agreements[agreementId]; + // Idempotent: already accepted at this hash → return silently, no state change. + // Short-circuits before deadline + auth; callable after the RCA deadline. + if (agreement.state == AgreementState.Accepted && agreement.activeTermsHash == rcaHash) return agreementId; + + require( + block.timestamp <= rca.deadline, + RecurringCollectorAgreementDeadlineElapsed(block.timestamp, rca.deadline) + ); + _requireAuthorization(rca.payer, rcaHash, signature, agreementId, OFFER_TYPE_NEW); - _validateAndStoreAgreement(rca, agreementId, rcaHash); + if (_validateAndStoreTerms(agreementId, rcaHash, _termsFromRCA(rca), rca.payer, VERSION_CURRENT, rca.deadline)) + _storeAgreement(agreementId, rca); - AgreementData storage agreement = _getStorage().agreements[agreementId]; - // Idempotent: already accepted → return silently - if (agreement.state == AgreementState.Accepted) return agreementId; require( agreement.state == AgreementState.NotAccepted, RecurringCollectorAgreementIncorrectState(agreementId, agreement.state) @@ -251,19 +264,13 @@ contract RecurringCollector is } /** - * @notice Validates RCA fields and registers the agreement (identity + terms). - * Does not flip state to Accepted — caller handles the accept step. - * @param _rca The Recurring Collection Agreement to validate and store - * @param agreementId The deterministic agreement ID - * @param _rcaHash The EIP-712 hash of the RCA + * @notice Stores agreement participants and state identity. Does not store terms or hashes. + * @param agreementId The agreement ID + * @param rca The Recurring Collection Agreement (source for identity fields) */ - function _validateAndStoreAgreement( - RecurringCollectionAgreement memory _rca, - bytes16 agreementId, - bytes32 _rcaHash - ) private { + function _storeAgreement(bytes16 agreementId, RecurringCollectionAgreement memory rca) private { require( - _rca.dataService != address(0) && _rca.payer != address(0) && _rca.serviceProvider != address(0), + rca.dataService != address(0) && rca.payer != address(0) && rca.serviceProvider != address(0), RecurringCollectorAgreementAddressNotSet() ); @@ -273,21 +280,9 @@ contract RecurringCollector is RecurringCollectorAgreementIncorrectState(agreementId, agreement.state) ); - _requireValidTerms( - _rca.deadline, _rca.endsAt, _rca.minSecondsPerCollection, _rca.maxSecondsPerCollection, - _rca.payer, _rca.conditions, _rca.maxOngoingTokensPerSecond - ); - - agreement.dataService = _rca.dataService; - agreement.payer = _rca.payer; - agreement.serviceProvider = _rca.serviceProvider; - agreement.endsAt = _rca.endsAt; - agreement.maxInitialTokens = _rca.maxInitialTokens; - agreement.maxOngoingTokensPerSecond = _rca.maxOngoingTokensPerSecond; - agreement.minSecondsPerCollection = _rca.minSecondsPerCollection; - agreement.maxSecondsPerCollection = _rca.maxSecondsPerCollection; - agreement.conditions = _rca.conditions; - agreement.activeTermsHash = _rcaHash; + agreement.dataService = rca.dataService; + agreement.payer = rca.payer; + agreement.serviceProvider = rca.serviceProvider; agreement.updateNonce = 0; } @@ -346,7 +341,22 @@ contract RecurringCollector is RecurringCollectorInvalidUpdateNonce(rcau.agreementId, expectedNonce, rcau.nonce) ); - _validateAndStoreUpdate(agreement, rcau, rcauHash); + RecurringCollectorStorage storage $ = _getStorage(); + if (agreement.pendingTermsHash == rcauHash) { + // Accept pending offer: move hash from pending to active (terms already stored + validated) + delete $.terms[agreement.activeTermsHash]; + agreement.activeTermsHash = rcauHash; + agreement.pendingTermsHash = bytes32(0); + } else + _validateAndStoreTerms( + rcau.agreementId, + rcauHash, + _termsFromRCAU(rcau), + agreement.payer, + VERSION_CURRENT, + rcau.deadline + ); + agreement.updateNonce = rcau.nonce; emit AgreementUpdated( @@ -437,26 +447,18 @@ contract RecurringCollector is * @return details The agreement details */ function _offerNew(bytes calldata _data) private returns (AgreementDetails memory details) { - RecurringCollectorStorage storage $ = _getStorage(); RecurringCollectionAgreement memory rca = abi.decode(_data, (RecurringCollectionAgreement)); require( block.timestamp <= rca.deadline, RecurringCollectorAgreementDeadlineElapsed(block.timestamp, rca.deadline) ); - _requirePayerToSupportEligibilityCheck(rca.payer, rca.conditions); (bytes16 agreementId, bytes32 rcaHash) = _rcaIdAndHash(rca); - $.rcaOffers[agreementId] = StoredOffer({ offerHash: rcaHash, data: _data }); + if (_validateAndStoreTerms(agreementId, rcaHash, _termsFromRCA(rca), rca.payer, VERSION_CURRENT, rca.deadline)) + _storeAgreement(agreementId, rca); - details.agreementId = agreementId; - details.payer = rca.payer; - details.dataService = rca.dataService; - details.serviceProvider = rca.serviceProvider; - details.versionHash = rcaHash; - details.state = REGISTERED; - - emit OfferStored(agreementId, rca.payer, OFFER_TYPE_NEW, rcaHash); + return _getAgreementDetails(_getStorage(), agreementId, rcaHash); } /** @@ -465,7 +467,6 @@ contract RecurringCollector is * @return details The agreement details */ function _offerUpdate(bytes calldata _data) private returns (AgreementDetails memory details) { - RecurringCollectorStorage storage $ = _getStorage(); RecurringCollectionAgreementUpdate memory rcau = abi.decode(_data, (RecurringCollectionAgreementUpdate)); require( block.timestamp <= rcau.deadline, @@ -473,111 +474,111 @@ contract RecurringCollector is ); bytes16 agreementId = rcau.agreementId; - // Payer check: look up the existing agreement or the stored RCA offer - AgreementData storage agreement = $.agreements[agreementId]; + AgreementData storage agreement = _getStorage().agreements[agreementId]; address payer = agreement.payer; - if (payer == address(0)) { - // Not yet accepted — check stored RCA offer payer - require( - $.rcaOffers[agreementId].offerHash != bytes32(0), - RecurringCollectorAgreementIncorrectState(agreementId, AgreementState.NotAccepted) - ); - RecurringCollectionAgreement memory rca = abi.decode( - $.rcaOffers[agreementId].data, - (RecurringCollectionAgreement) - ); - payer = rca.payer; - details.dataService = rca.dataService; - details.serviceProvider = rca.serviceProvider; - } else { - details.dataService = agreement.dataService; - details.serviceProvider = agreement.serviceProvider; - } - _requirePayerToSupportEligibilityCheck(payer, rcau.conditions); bytes32 offerHash = _hashRCAU(rcau); - $.rcauOffers[agreementId] = StoredOffer({ offerHash: offerHash, data: _data }); - - details.agreementId = agreementId; - details.payer = payer; - details.versionHash = offerHash; - details.state = REGISTERED | UPDATE; + _validateAndStoreTerms(agreementId, offerHash, _termsFromRCAU(rcau), payer, VERSION_NEXT, rcau.deadline); - emit OfferStored(agreementId, payer, OFFER_TYPE_UPDATE, offerHash); + return _getAgreementDetails(_getStorage(), agreementId, offerHash); } /// @inheritdoc IAgreementCollector function cancel(bytes16 agreementId, bytes32 termsHash, uint16 options) external whenNotPaused { RecurringCollectorStorage storage $ = _getStorage(); AgreementData storage agreement = $.agreements[agreementId]; - _requirePayer($, agreement, agreementId); - - if (agreement.activeTermsHash != termsHash) { - if (options & SCOPE_PENDING != 0) - // Pending scope: delete stored offer if hash matches and terms are not currently active - if ($.rcaOffers[agreementId].offerHash == termsHash) delete $.rcaOffers[agreementId]; - else if ($.rcauOffers[agreementId].offerHash == termsHash) delete $.rcauOffers[agreementId]; - } else if (options & SCOPE_ACTIVE != 0 && agreement.state == AgreementState.Accepted) - // Active scope and hash matches: cancel accepted agreement - IDataServiceAgreements(agreement.dataService).cancelIndexingAgreementByPayer(agreementId); - } - /** - * @notice Requires that msg.sender is the payer for an agreement. - * @dev Checks the on-chain agreement first, then falls back to stored RCA offer. - * @param agreement The agreement data - * @param agreementId The agreement ID - */ - // solhint-disable-next-line use-natspec - function _requirePayer( - RecurringCollectorStorage storage $, - AgreementData storage agreement, - bytes16 agreementId - ) private view { - if (agreement.payer == msg.sender) return; - - // Not payer on accepted agreement — check stored RCA offer - StoredOffer storage rcaOffer = $.rcaOffers[agreementId]; - if (rcaOffer.offerHash != bytes32(0)) { - RecurringCollectionAgreement memory rca = abi.decode(rcaOffer.data, (RecurringCollectionAgreement)); - require(msg.sender == rca.payer, RecurringCollectorUnauthorizedCaller(msg.sender, rca.payer)); - return; + // Pending / active scopes: revert if on-chain data exists but caller is not the payer. + // No-op if nothing exists on-chain (nothing to cancel). + if (options & (SCOPE_PENDING | SCOPE_ACTIVE) != 0) { + address payer = agreement.payer; + if (payer == address(0)) return; + require(payer == msg.sender, RecurringCollectorUnauthorizedCaller(msg.sender, payer)); } - if (agreement.payer == address(0)) return; - revert RecurringCollectorUnauthorizedCaller(msg.sender, agreement.payer); + if (options & SCOPE_PENDING != 0) { + if (agreement.pendingTermsHash == termsHash) { + // Pending update matches — delete it. + delete $.terms[termsHash]; + agreement.pendingTermsHash = bytes32(0); + emit OfferCancelled(msg.sender, agreementId, termsHash); + } else if (agreement.activeTermsHash == termsHash && agreement.state == AgreementState.NotAccepted) { + // Pre-acceptance RCA offer matches — delete it. Any pending RCAU is + // independent and can be cancelled separately in either order. + delete $.terms[termsHash]; + agreement.activeTermsHash = bytes32(0); + emit OfferCancelled(msg.sender, agreementId, termsHash); + } + } + if ( + options & SCOPE_ACTIVE != 0 && + agreement.state == AgreementState.Accepted && + agreement.activeTermsHash == termsHash + ) + // Active scope and hash matches accepted agreement — cancel via data service. + IDataServiceAgreements(agreement.dataService).cancelIndexingAgreementByPayer(agreementId); } /// @inheritdoc IAgreementCollector function getAgreementDetails( bytes16 agreementId, - uint256 /* index */ + uint256 index ) external view returns (AgreementDetails memory details) { RecurringCollectorStorage storage $ = _getStorage(); AgreementData storage agreement = $.agreements[agreementId]; - if (agreement.state != AgreementState.NotAccepted) { - details.agreementId = agreementId; - details.payer = agreement.payer; - details.dataService = agreement.dataService; - details.serviceProvider = agreement.serviceProvider; - details.versionHash = agreement.activeTermsHash; - details.state = ACCEPTED; - return details; - } + return _getAgreementDetails($, agreementId, _getVersionHash(agreement, index)); + } - // Not yet accepted — check stored RCA offer - StoredOffer storage rcaOffer = $.rcaOffers[agreementId]; - if (rcaOffer.offerHash != bytes32(0)) { - RecurringCollectionAgreement memory rca = abi.decode(rcaOffer.data, (RecurringCollectionAgreement)); - details.agreementId = agreementId; - details.payer = rca.payer; - details.dataService = rca.dataService; - details.serviceProvider = rca.serviceProvider; - details.versionHash = rcaOffer.offerHash; - details.state = REGISTERED; - } + /** + * @notice Build an AgreementDetails view for a given version hash. + * @dev Empty struct when no offer is stored for the version. Otherwise composes state flags + * per the {IAgreementCollector} flag spec. Shared by getAgreementDetails, _offerNew, and + * _offerUpdate so all three consistently reflect the same on-chain state. + * @param $ The collector storage root + * @param agreementId The agreement ID + * @param versionHash The version hash (activeTermsHash or pendingTermsHash) + * @return details The composed agreement details + */ + // solhint-disable-next-line use-natspec + function _getAgreementDetails( + RecurringCollectorStorage storage $, + bytes16 agreementId, + bytes32 versionHash + ) private view returns (AgreementDetails memory details) { + uint8 offerType = $.terms[versionHash].offerType; + if (offerType == OFFER_TYPE_NONE) return details; + + AgreementData storage agreement = $.agreements[agreementId]; + AgreementState agreementState = agreement.state; + + details.agreementId = agreementId; + details.versionHash = versionHash; + details.payer = agreement.payer; + details.dataService = agreement.dataService; + details.serviceProvider = agreement.serviceProvider; + + details.state = REGISTERED; + if (agreementState != AgreementState.NotAccepted && versionHash == agreement.activeTermsHash) + details.state |= ACCEPTED; + + if (offerType == OFFER_TYPE_UPDATE) details.state |= UPDATE; + } + + /** + * @notice Resolves a version index to the corresponding terms hash. + * @dev Returns bytes32(0) for unknown indexes or when the slot is empty. + * @param agreement The agreement data + * @param index The version index ({VERSION_CURRENT} or {VERSION_NEXT}) + * @return versionHash The resolved terms hash + */ + function _getVersionHash( + AgreementData storage agreement, + uint256 index + ) private view returns (bytes32 versionHash) { + if (index == VERSION_CURRENT) versionHash = agreement.activeTermsHash; + else if (index == VERSION_NEXT) versionHash = agreement.pendingTermsHash; } /// @inheritdoc IAgreementCollector @@ -591,13 +592,13 @@ contract RecurringCollector is uint256 index ) external view returns (uint8 offerType, bytes memory offerData) { RecurringCollectorStorage storage $ = _getStorage(); - if (index == OFFER_TYPE_NEW) { - StoredOffer storage rca = $.rcaOffers[agreementId]; - if (rca.offerHash != bytes32(0)) return (OFFER_TYPE_NEW, rca.data); - } else if (index == OFFER_TYPE_UPDATE) { - StoredOffer storage rcau = $.rcauOffers[agreementId]; - if (rcau.offerHash != bytes32(0)) return (OFFER_TYPE_UPDATE, rcau.data); - } + AgreementData storage agreement = $.agreements[agreementId]; + + bytes32 versionHash = _getVersionHash(agreement, index); + if (versionHash == bytes32(0)) return (OFFER_TYPE_NONE, ""); + + AgreementTerms storage stored = $.terms[versionHash]; + return (stored.offerType, stored.data); } /** @@ -747,7 +748,7 @@ contract RecurringCollector is // copy to 32 bytes, preventing returndata bombing. Only an explicit return of 0 blocks // collection; reverts, short returndata, and malformed responses are treated as "no // opinion" (collection proceeds). - if ((agreement.conditions & CONDITION_ELIGIBILITY_CHECK) != 0) { + if ((_getStorage().terms[agreement.activeTermsHash].conditions & CONDITION_ELIGIBILITY_CHECK) != 0) { if (gasleft() < (MAX_PAYER_CALLBACK_GAS * 64) / 63 + CALLBACK_GAS_OVERHEAD) revert RecurringCollectorInsufficientCallbackGas(); bytes memory cd = abi.encodeCall(IProviderEligibility.isEligible, (provider)); @@ -850,8 +851,8 @@ contract RecurringCollector is /** * @notice Validates offer terms: collection window, eligibility support, and overflow. - * @dev Called by _validateAndStoreAgreement and _validateAndStoreUpdate. Time-independent — - * validates against the offer's deadline so the check is stable across the offer's lifetime. + * @dev Called once per unique hash by _validateAndStoreTerms (on first store). Time-independent + * — validates against the offer's deadline. * @param _deadline The offer's acceptance deadline * @param _endsAt The end time of the agreement * @param _minSecondsPerCollection The minimum seconds per collection @@ -875,12 +876,44 @@ contract RecurringCollector is _maxOngoingTokensPerSecond * _maxSecondsPerCollection * 1024; } + /// @notice Extract AgreementTerms from an RCA. + /// @param rca The Recurring Collection Agreement + /// @return terms The decoded agreement terms + function _termsFromRCA(RecurringCollectionAgreement memory rca) private pure returns (AgreementTerms memory terms) { + terms.offerType = OFFER_TYPE_NEW; + terms.endsAt = rca.endsAt; + terms.deadline = rca.deadline; + terms.minSecondsPerCollection = rca.minSecondsPerCollection; + terms.maxSecondsPerCollection = rca.maxSecondsPerCollection; + terms.conditions = rca.conditions; + terms.maxInitialTokens = rca.maxInitialTokens; + terms.maxOngoingTokensPerSecond = rca.maxOngoingTokensPerSecond; + terms.data = abi.encode(rca); + } + + /// @notice Extract AgreementTerms from an RCAU. + /// @param rcau The Recurring Collection Agreement Update + /// @return terms The decoded agreement terms + function _termsFromRCAU( + RecurringCollectionAgreementUpdate memory rcau + ) private pure returns (AgreementTerms memory terms) { + terms.offerType = OFFER_TYPE_UPDATE; + terms.endsAt = rcau.endsAt; + terms.deadline = rcau.deadline; + terms.minSecondsPerCollection = rcau.minSecondsPerCollection; + terms.maxSecondsPerCollection = rcau.maxSecondsPerCollection; + terms.conditions = rcau.conditions; + terms.maxInitialTokens = rcau.maxInitialTokens; + terms.maxOngoingTokensPerSecond = rcau.maxOngoingTokensPerSecond; + terms.data = abi.encode(rcau); + } + /** * @notice Validates temporal constraints and caps the requested token amount. * @dev Enforces `minSecondsPerCollection` (unless canceled/elapsed) and returns the lesser of * the requested amount and the RCA payer's per-collection cap * (`maxOngoingTokensPerSecond * collectionSeconds`, plus `maxInitialTokens` on first collection). - * @param _agreement The agreement data + * @param _agreement The agreement data (lifecycle) * @param _agreementId The ID of the agreement * @param _tokens The requested token amount (upper bound from data service) * @param _collectionSeconds Collection duration, already capped at maxSecondsPerCollection @@ -892,23 +925,23 @@ contract RecurringCollector is uint256 _tokens, uint256 _collectionSeconds ) private view returns (uint256) { - bool canceledOrElapsed = _agreement.state == AgreementState.CanceledByPayer || - block.timestamp > _agreement.endsAt; + AgreementTerms storage terms = _getStorage().terms[_agreement.activeTermsHash]; + bool canceledOrElapsed = _agreement.state == AgreementState.CanceledByPayer || block.timestamp > terms.endsAt; if (!canceledOrElapsed) { require( - _collectionSeconds >= _agreement.minSecondsPerCollection, + _collectionSeconds >= terms.minSecondsPerCollection, RecurringCollectorCollectionTooSoon( _agreementId, // casting to uint32 is safe because _collectionSeconds < minSecondsPerCollection (uint32) // forge-lint: disable-next-line(unsafe-typecast) uint32(_collectionSeconds), - _agreement.minSecondsPerCollection + terms.minSecondsPerCollection ) ); } // _collectionSeconds is already capped at maxSecondsPerCollection by _getCollectionInfo - uint256 maxTokens = _agreement.maxOngoingTokensPerSecond * _collectionSeconds; - maxTokens += _agreement.lastCollectionAt == 0 ? _agreement.maxInitialTokens : 0; + uint256 maxTokens = terms.maxOngoingTokensPerSecond * _collectionSeconds; + maxTokens += _agreement.lastCollectionAt == 0 ? terms.maxInitialTokens : 0; return Math.min(_tokens, maxTokens); } @@ -1009,9 +1042,9 @@ contract RecurringCollector is * @notice Verifies authorization for an EIP712 hash using the given basis. * @param _payer The payer address (signer owner for ECDSA, contract for approval) * @param _hash The EIP712 typed data hash - * @param _signature The ECDSA signature bytes, zero length for no signature (pre-approved via stored offer) - * @param _agreementId The agreement ID (used to look up stored offer when not signed) - * @param _offerType OFFER_TYPE_NEW or OFFER_TYPE_UPDATE (selects which stored offer to check) + * @param _signature The ECDSA signature, zero length for no signature (pre-approved via stored terms) + * @param _agreementId The agreement ID (used to look up stored terms when not signed) + * @param _offerType OFFER_TYPE_NEW or OFFER_TYPE_UPDATE (selects which terms hash to check) */ function _requireAuthorization( address _payer, @@ -1020,17 +1053,14 @@ contract RecurringCollector is bytes16 _agreementId, uint8 _offerType ) private view { - RecurringCollectorStorage storage $ = _getStorage(); - if (0 < _signature.length) require(_isAuthorized(_payer, ECDSA.recover(_hash, _signature)), RecurringCollectorInvalidSigner()); - else - // Check stored offer hash instead of callback - require( - (_offerType == OFFER_TYPE_NEW ? $.rcaOffers[_agreementId] : $.rcauOffers[_agreementId]).offerHash == - _hash, - RecurringCollectorInvalidSigner() - ); + else { + // Pre-approval: the hash must match the expected version of this agreement. + AgreementData storage agreement = _getStorage().agreements[_agreementId]; + bytes32 versionHash = _offerType == OFFER_TYPE_NEW ? agreement.activeTermsHash : agreement.pendingTermsHash; + require(versionHash == _hash, RecurringCollectorInvalidSigner()); + } } /** @@ -1052,41 +1082,56 @@ contract RecurringCollector is } /** - * @notice Validates and stores an update to a Recurring Collection Agreement. - * Shared validation/storage/emit logic for the update function. - * @param _agreement The storage reference to the agreement data - * @param _rcau The Recurring Collection Agreement Update to apply - * @param _rcauHash The EIP-712 hash of the RCAU + * @notice Validate, store, and link agreement terms to a version slot. + * @dev Idempotent: if the hash is already stored for this agreement (in any slot), returns + * false without side effects. Validation is time-independent (uses deadline) so terms only + * need validating once — on first store. Caller must separately handle the "accept pending + * into active" transition (done in {update}): this function is only for storing new terms. + * @param agreementId The agreement ID + * @param hash The EIP-712 terms hash + * @param newTerms The decoded terms to store + * @param payer The payer address (for eligibility validation) + * @param index The version slot to link: {VERSION_CURRENT} (active) or {VERSION_NEXT} (pending). + * @param deadline The offer deadline (for offer-window validation) + * @return added False if the hash was already stored (idempotent no-op). */ - function _validateAndStoreUpdate( - AgreementData storage _agreement, - RecurringCollectionAgreementUpdate calldata _rcau, - bytes32 _rcauHash - ) private { + function _validateAndStoreTerms( + bytes16 agreementId, + bytes32 hash, + AgreementTerms memory newTerms, + address payer, + uint256 index, + uint64 deadline + ) private returns (bool added) { RecurringCollectorStorage storage $ = _getStorage(); + if ($.terms[hash].offerType != OFFER_TYPE_NONE) return false; + _requireValidTerms( - _rcau.deadline, _rcau.endsAt, _rcau.minSecondsPerCollection, _rcau.maxSecondsPerCollection, - _agreement.payer, _rcau.conditions, _rcau.maxOngoingTokensPerSecond + deadline, + newTerms.endsAt, + newTerms.minSecondsPerCollection, + newTerms.maxSecondsPerCollection, + payer, + newTerms.conditions, + newTerms.maxOngoingTokensPerSecond ); + $.terms[hash] = newTerms; - // Clean up stored replaced offer — oldHash is always non-zero for accepted agreements - bytes32 oldHash = _agreement.activeTermsHash; - if ($.rcaOffers[_rcau.agreementId].offerHash == oldHash) delete $.rcaOffers[_rcau.agreementId]; - else if ($.rcauOffers[_rcau.agreementId].offerHash == oldHash) delete $.rcauOffers[_rcau.agreementId]; - - // update the agreement terms - _agreement.endsAt = _rcau.endsAt; - _agreement.maxInitialTokens = _rcau.maxInitialTokens; - _agreement.maxOngoingTokensPerSecond = _rcau.maxOngoingTokensPerSecond; - _agreement.minSecondsPerCollection = _rcau.minSecondsPerCollection; - _agreement.maxSecondsPerCollection = _rcau.maxSecondsPerCollection; - _agreement.conditions = _rcau.conditions; - _agreement.activeTermsHash = _rcauHash; + AgreementData storage agreement = $.agreements[agreementId]; + if (index == VERSION_NEXT) { + if (agreement.pendingTermsHash != bytes32(0)) delete $.terms[agreement.pendingTermsHash]; + agreement.pendingTermsHash = hash; + } else { + if (agreement.activeTermsHash != bytes32(0)) delete $.terms[agreement.activeTermsHash]; + agreement.activeTermsHash = hash; + } + emit OfferStored(agreementId, payer, newTerms.offerType, hash); + return true; } /** - * @notice Gets an agreement to be updated. + * @notice Gets an agreement storage reference. * @param _agreementId The ID of the agreement to get * @return The storage reference to the agreement data */ @@ -1108,7 +1153,7 @@ contract RecurringCollector is * @dev Single source of truth for collection window logic. The returned `collectionSeconds` * is capped at `maxSecondsPerCollection` — this is a cap on tokens, not a deadline; late * collections succeed but receive at most `maxSecondsPerCollection` worth of tokens. - * @param _agreement The agreement data + * @param _agreement The agreement data (lifecycle); terms loaded from terms[activeTermsHash] * @return isCollectable Whether the agreement can be collected from * @return collectionSeconds The valid collection duration in seconds, capped at * maxSecondsPerCollection (0 if not collectable) @@ -1125,13 +1170,14 @@ contract RecurringCollector is return (false, 0, AgreementNotCollectableReason.InvalidAgreementState); } - bool canceledOrElapsed = _agreement.state == AgreementState.CanceledByPayer || - block.timestamp > _agreement.endsAt; + AgreementTerms storage _terms = _getStorage().terms[_agreement.activeTermsHash]; + + bool canceledOrElapsed = _agreement.state == AgreementState.CanceledByPayer || block.timestamp > _terms.endsAt; uint256 canceledOrNow = _agreement.state == AgreementState.CanceledByPayer ? _agreement.canceledAt : block.timestamp; - uint256 collectionEnd = canceledOrElapsed ? Math.min(canceledOrNow, _agreement.endsAt) : block.timestamp; + uint256 collectionEnd = canceledOrElapsed ? Math.min(canceledOrNow, _terms.endsAt) : block.timestamp; uint256 collectionStart = _agreementCollectionStartAt(_agreement); if (collectionEnd < collectionStart) { @@ -1143,11 +1189,7 @@ contract RecurringCollector is } uint256 elapsed = collectionEnd - collectionStart; - return ( - true, - Math.min(elapsed, uint256(_agreement.maxSecondsPerCollection)), - AgreementNotCollectableReason.None - ); + return (true, Math.min(elapsed, uint256(_terms.maxSecondsPerCollection)), AgreementNotCollectableReason.None); } /** @@ -1159,46 +1201,11 @@ contract RecurringCollector is return _agreement.lastCollectionAt > 0 ? _agreement.lastCollectionAt : _agreement.acceptedAt; } - /** - * @notice Compute the maximum tokens collectable in the next collection (worst case). - * @dev Determines the collection window from agreement state, then delegates to {_maxClaim}. - * Returns 0 for non-collectable states. - * @param _a The agreement data - * @return The maximum tokens that could be collected - */ - function _getMaxNextClaim(AgreementData storage _a) private view returns (uint256) { - // CanceledByServiceProvider = immediately non-collectable - if (_a.state == AgreementState.CanceledByServiceProvider) return 0; - // Only Accepted and CanceledByPayer are collectable - if (_a.state != AgreementState.Accepted && _a.state != AgreementState.CanceledByPayer) return 0; - - uint256 collectionStart = _agreementCollectionStartAt(_a); - - // Determine the latest possible collection end - uint256 collectionEnd; - if (_a.state == AgreementState.CanceledByPayer) { - // Payer cancel freezes the window at min(canceledAt, endsAt) - collectionEnd = _a.canceledAt < _a.endsAt ? _a.canceledAt : _a.endsAt; - } else { - // Active: collection window capped at endsAt - collectionEnd = _a.endsAt; - } - - return - _maxClaim( - collectionStart, - collectionEnd, - _a.maxSecondsPerCollection, - _a.maxOngoingTokensPerSecond, - _a.lastCollectionAt == 0 ? _a.maxInitialTokens : 0 - ); - } - /** * @notice Compute max next claim with scope control (active, pending, or both). - * @dev Adapts the refactored _getMaxNextClaim(agreementId, agreementScope) pattern. - * Active claim comes from the on-chain agreement state. Pending claim comes from - * stored offers (RCA if not yet accepted, RCAU if pending update). + * @dev Terms are loaded from terms[hash]. Active window is state-dependent (pre-acceptance + * time-caps from now; accepted starts from last collection; cancelled caps at canceledAt or + * yields a zero-width window). Pending window is always time-capped from now to endsAt. * @param agreementId The agreement ID * @param agreementScope Bitmask: SCOPE_ACTIVE (1), SCOPE_PENDING (2), or both (3) * @return maxClaim The maximum tokens claimable under the requested scope @@ -1208,75 +1215,60 @@ contract RecurringCollector is RecurringCollectorStorage storage $ = _getStorage(); AgreementData storage _a = $.agreements[agreementId]; - - uint256 maxActiveClaim = 0; - uint256 maxPendingClaim = 0; - - if (agreementScope & SCOPE_ACTIVE != 0) { - if (_a.state == AgreementState.NotAccepted) { - // Not yet accepted — check stored RCA offer - StoredOffer storage rcaOffer = $.rcaOffers[agreementId]; - if (rcaOffer.offerHash != bytes32(0)) { - RecurringCollectionAgreement memory rca = abi.decode(rcaOffer.data, (RecurringCollectionAgreement)); - // Use block.timestamp as proxy for acceptedAt, deadline as expiry - if (block.timestamp < rca.deadline) { - maxActiveClaim = _maxClaim( - block.timestamp, - rca.endsAt, - rca.maxSecondsPerCollection, - rca.maxOngoingTokensPerSecond, - rca.maxInitialTokens - ); - } - } - } else { - maxActiveClaim = _getMaxNextClaim(_a); - } + bool hasCollected = _a.lastCollectionAt != 0; + + if (agreementScope & SCOPE_ACTIVE != 0 && _a.activeTermsHash != bytes32(0)) { + AgreementTerms storage terms = $.terms[_a.activeTermsHash]; + AgreementState state = _a.state; + // Pre-acceptance: claimable while accept() is still admissible (block.timestamp <= + // deadline); past the deadline, start == end → zero window. + // Accepted / CanceledByPayer: start from last collection (or acceptedAt). + uint256 start = state == AgreementState.NotAccepted + ? (block.timestamp <= terms.deadline ? block.timestamp : terms.endsAt) + : _agreementCollectionStartAt(_a); + // CanceledByServiceProvider: zero window (end == start). CanceledByPayer: cap at canceledAt. + uint256 end = state == AgreementState.CanceledByServiceProvider + ? start + : ( + state == AgreementState.CanceledByPayer && _a.canceledAt < terms.endsAt + ? _a.canceledAt + : terms.endsAt + ); + maxClaim = _maxClaimForTerms(start, end, terms, hasCollected); } - if (agreementScope & SCOPE_PENDING != 0) { - StoredOffer storage rcauOffer = $.rcauOffers[agreementId]; - if (rcauOffer.offerHash != bytes32(0)) { - RecurringCollectionAgreementUpdate memory rcau = abi.decode( - rcauOffer.data, - (RecurringCollectionAgreementUpdate) - ); - // Ongoing claim: time-capped from now to rcau.endsAt - maxPendingClaim = _maxClaim( - block.timestamp, - rcau.endsAt, - rcau.maxSecondsPerCollection, - rcau.maxOngoingTokensPerSecond, - _a.lastCollectionAt == 0 ? rcau.maxInitialTokens : 0 - ); + if (agreementScope & SCOPE_PENDING != 0 && _a.pendingTermsHash != bytes32(0)) { + AgreementTerms storage terms = $.terms[_a.pendingTermsHash]; + // Skip expired pending — {update} rejects past-deadline RCAUs, so a stale pending + // offer can never be promoted to active and cannot be claimed on. + if (block.timestamp <= terms.deadline) { + uint256 pending = _maxClaimForTerms(block.timestamp, terms.endsAt, terms, hasCollected); + if (maxClaim < pending) maxClaim = pending; } } - - maxClaim = maxActiveClaim < maxPendingClaim ? maxPendingClaim : maxActiveClaim; } /** - * @notice Core claim formula: rate * min(window, maxSeconds) + initialBonus. - * @dev Single source of truth for all max-claim calculations. Returns 0 when - * windowEnd <= windowStart (empty or inverted window). + * @notice Compute max claim from a window and agreement terms. * @param windowStart Start of the collection window * @param windowEnd End of the collection window - * @param maxSecondsPerCollection Maximum seconds per collection period - * @param maxOngoingTokensPerSecond Maximum ongoing tokens per second - * @param maxInitialTokens Initial bonus tokens (0 if already collected) - * @return The maximum possible claim amount + * @param terms The agreement terms + * @param hasCollected Whether a collection has already occurred (suppresses initial bonus) + * @return maxClaim The maximum possible claim amount */ - function _maxClaim( + function _maxClaimForTerms( uint256 windowStart, uint256 windowEnd, - uint256 maxSecondsPerCollection, - uint256 maxOngoingTokensPerSecond, - uint256 maxInitialTokens - ) private pure returns (uint256) { + AgreementTerms storage terms, + bool hasCollected + ) private view returns (uint256 maxClaim) { if (windowEnd <= windowStart) return 0; uint256 windowSeconds = windowEnd - windowStart; - uint256 effectiveSeconds = windowSeconds < maxSecondsPerCollection ? windowSeconds : maxSecondsPerCollection; - return maxOngoingTokensPerSecond * effectiveSeconds + maxInitialTokens; + uint256 effectiveSeconds = windowSeconds < terms.maxSecondsPerCollection + ? windowSeconds + : terms.maxSecondsPerCollection; + maxClaim = terms.maxOngoingTokensPerSecond * effectiveSeconds; + if (!hasCollected) maxClaim += terms.maxInitialTokens; } /** diff --git a/packages/horizon/test/unit/payments/recurring-collector/accept.t.sol b/packages/horizon/test/unit/payments/recurring-collector/accept.t.sol index 6ff215170..fb7c06cb1 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/accept.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/accept.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.27; import { IRecurringCollector } from "@graphprotocol/interfaces/contracts/horizon/IRecurringCollector.sol"; +import { OFFER_TYPE_NEW } from "@graphprotocol/interfaces/contracts/horizon/IAgreementCollector.sol"; import { RecurringCollectorSharedTest } from "./shared.t.sol"; @@ -65,5 +66,171 @@ contract RecurringCollectorAcceptTest is RecurringCollectorSharedTest { assertEq(vm.getRecordedLogs().length, 0, "no event emitted on idempotent re-accept"); } + /// @notice Re-accepting an already-accepted RCA at the same hash must still succeed after + /// the RCA's acceptance deadline has elapsed. The idempotent short-circuit runs before the + /// deadline check so signature lifetime is not consumed — this is the path the SubgraphService + /// relies on to rebind an agreement to a new allocation after the original acceptance window + /// has closed. + function test_Accept_Idempotent_AfterDeadline_SameHash(FuzzyTestAccept calldata fuzzyTestAccept) public { + ( + IRecurringCollector.RecurringCollectionAgreement memory acceptedRca, + bytes memory signature, + , + bytes16 agreementId + ) = _sensibleAuthorizeAndAccept(fuzzyTestAccept); + + // Warp past the RCA's deadline — a fresh accept would now revert with + // RecurringCollectorAgreementDeadlineElapsed. + vm.warp(uint256(acceptedRca.deadline) + 1); + + vm.recordLogs(); + vm.prank(acceptedRca.dataService); + bytes16 returnedId = _recurringCollector.accept(acceptedRca, signature); + assertEq(returnedId, agreementId, "returns the same agreementId"); + assertEq(vm.getRecordedLogs().length, 0, "no event emitted on idempotent re-accept after deadline"); + + // Sanity: the collector-side agreement is still in Accepted state, unchanged by the no-op. + IRecurringCollector.AgreementData memory agreement = _recurringCollector.getAgreement(agreementId); + assertEq(uint8(agreement.state), uint8(IRecurringCollector.AgreementState.Accepted)); + } + + /// @notice A fresh accept (no prior offer()) stores terms via _validateAndStoreTerms, which must + /// emit OfferStored. AgreementAccepted follows. Both events observable in order. + function test_Accept_EmitsOfferStored_WhenFreshTerms(FuzzyTestAccept calldata fuzzyTestAccept) public { + IRecurringCollector.RecurringCollectionAgreement memory rca = _recurringCollectorHelper.sensibleRCA( + fuzzyTestAccept.rca + ); + uint256 signerKey = boundKey(fuzzyTestAccept.unboundedSignerKey); + _recurringCollectorHelper.authorizeSignerWithChecks(rca.payer, signerKey); + (, bytes memory signature) = _recurringCollectorHelper.generateSignedRCA(rca, signerKey); + bytes32 rcaHash = _recurringCollector.hashRCA(rca); + bytes16 agreementId = _recurringCollector.generateAgreementId( + rca.payer, + rca.dataService, + rca.serviceProvider, + rca.deadline, + rca.nonce + ); + _setupValidProvision(rca.serviceProvider, rca.dataService); + + // OfferStored fires from _validateAndStoreTerms before _storeAgreement; AgreementAccepted + // follows the state transition at the end of accept(). + vm.expectEmit(address(_recurringCollector)); + emit IRecurringCollector.OfferStored(agreementId, rca.payer, OFFER_TYPE_NEW, rcaHash); + vm.expectEmit(address(_recurringCollector)); + emit IRecurringCollector.AgreementAccepted( + rca.dataService, + rca.payer, + rca.serviceProvider, + agreementId, + rca.endsAt, + rca.maxInitialTokens, + rca.maxOngoingTokensPerSecond, + rca.minSecondsPerCollection, + rca.maxSecondsPerCollection + ); + vm.prank(rca.dataService); + _recurringCollector.accept(rca, signature); + } + + /// @notice A second RCA sharing the same agreementId seed (payer, dataService, serviceProvider, + /// deadline, nonce) but with different other fields — so different rcaHash — must not be + /// accepted against an already-Accepted agreement. The idempotent short-circuit only fires on + /// exact hash match; everything else must fall through to the state guard and revert. Proves + /// the short-circuit can't be abused as an overwrite path even in an imagined 128-bit + /// agreementId collision. + function test_Accept_Revert_WhenDifferentHashSameAgreementId(FuzzyTestAccept calldata fuzzyTestAccept) public { + ( + IRecurringCollector.RecurringCollectionAgreement memory acceptedRca, + , + uint256 signerKey, + bytes16 agreementId + ) = _sensibleAuthorizeAndAccept(fuzzyTestAccept); + + // Snapshot the original hash before constructing the variant. `variant = acceptedRca` in + // Solidity memory is a reference copy, so rebuild explicitly to vary one pricing field + // while keeping the 5 agreementId-seed fields (payer, dataService, serviceProvider, + // deadline, nonce) verbatim. + bytes32 originalHash = _recurringCollector.hashRCA(acceptedRca); + IRecurringCollector.RecurringCollectionAgreement memory variant = IRecurringCollector + .RecurringCollectionAgreement({ + deadline: acceptedRca.deadline, + endsAt: acceptedRca.endsAt, + payer: acceptedRca.payer, + dataService: acceptedRca.dataService, + serviceProvider: acceptedRca.serviceProvider, + maxInitialTokens: acceptedRca.maxInitialTokens + 1, // <-- vary + maxOngoingTokensPerSecond: acceptedRca.maxOngoingTokensPerSecond, + minSecondsPerCollection: acceptedRca.minSecondsPerCollection, + maxSecondsPerCollection: acceptedRca.maxSecondsPerCollection, + conditions: acceptedRca.conditions, + nonce: acceptedRca.nonce, + metadata: acceptedRca.metadata + }); + + bytes32 variantHash = _recurringCollector.hashRCA(variant); + assertTrue(originalHash != variantHash, "hashes must differ when any field differs"); + assertEq( + _recurringCollector.generateAgreementId( + variant.payer, + variant.dataService, + variant.serviceProvider, + variant.deadline, + variant.nonce + ), + agreementId, + "same agreementId seed yields same id" + ); + + (, bytes memory variantSig) = _recurringCollectorHelper.generateSignedRCA(variant, signerKey); + + // Short-circuit doesn't fire (hash differs); falls through to _storeAgreement's state guard. + vm.expectRevert( + abi.encodeWithSelector( + IRecurringCollector.RecurringCollectorAgreementIncorrectState.selector, + agreementId, + IRecurringCollector.AgreementState.Accepted + ) + ); + vm.prank(acceptedRca.dataService); + _recurringCollector.accept(variant, variantSig); + + // Post-revert sanity: storage reflects the original, not the variant. + IRecurringCollector.AgreementData memory agreement = _recurringCollector.getAgreement(agreementId); + assertEq(agreement.activeTermsHash, originalHash, "activeTermsHash unchanged"); + } + + /// @notice After a cancellation, re-accepting the same RCA at the same hash must revert — the + /// short-circuit only fires when state == Accepted, so a cancelled agreement falls through to + /// the NotAccepted state guard. Proves cancelled is terminal and the short-circuit cannot + /// resurrect it. + function test_Accept_Revert_AfterCancellation_SameHash(FuzzyTestAccept calldata fuzzyTestAccept) public { + ( + IRecurringCollector.RecurringCollectionAgreement memory acceptedRca, + bytes memory signature, + , + bytes16 agreementId + ) = _sensibleAuthorizeAndAccept(fuzzyTestAccept); + + vm.prank(acceptedRca.dataService); + _recurringCollector.cancel(agreementId, IRecurringCollector.CancelAgreementBy.ServiceProvider); + + assertEq( + uint8(_recurringCollector.getAgreement(agreementId).state), + uint8(IRecurringCollector.AgreementState.CanceledByServiceProvider), + "precondition: cancelled" + ); + + vm.expectRevert( + abi.encodeWithSelector( + IRecurringCollector.RecurringCollectorAgreementIncorrectState.selector, + agreementId, + IRecurringCollector.AgreementState.CanceledByServiceProvider + ) + ); + vm.prank(acceptedRca.dataService); + _recurringCollector.accept(acceptedRca, signature); + } + /* solhint-enable graph/func-name-mixedcase */ } diff --git a/packages/horizon/test/unit/payments/recurring-collector/coverageGaps.t.sol b/packages/horizon/test/unit/payments/recurring-collector/coverageGaps.t.sol index 6596d47e8..cc693637f 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/coverageGaps.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/coverageGaps.t.sol @@ -849,6 +849,79 @@ contract RecurringCollectorCoverageGapsTest is RecurringCollectorSharedTest { assertEq(dataAfter.length, 0, "Offer data should be empty after cancel"); } + function test_Cancel_PendingRcaAndRcau_IndependentOrder() public { + MockAgreementOwner approver = new MockAgreementOwner(); + + IRecurringCollector.RecurringCollectionAgreement memory rca = IRecurringCollector.RecurringCollectionAgreement({ + deadline: uint64(block.timestamp + 1 hours), + endsAt: uint64(block.timestamp + 365 days), + payer: address(approver), + dataService: makeAddr("ds"), + serviceProvider: makeAddr("sp"), + maxInitialTokens: 100 ether, + maxOngoingTokensPerSecond: 1 ether, + minSecondsPerCollection: 600, + maxSecondsPerCollection: 3600, + conditions: 0, + nonce: 1, + metadata: "" + }); + + // Offer RCA (not yet accepted) + vm.prank(address(approver)); + IAgreementCollector.AgreementDetails memory details = _recurringCollector.offer( + OFFER_TYPE_NEW, + abi.encode(rca), + 0 + ); + bytes16 agreementId = details.agreementId; + bytes32 rcaHash = details.versionHash; + + // Offer RCAU on top of the pending RCA + IRecurringCollector.RecurringCollectionAgreementUpdate memory rcau = IRecurringCollector + .RecurringCollectionAgreementUpdate({ + agreementId: agreementId, + deadline: uint64(block.timestamp + 1 hours), + endsAt: uint64(block.timestamp + 365 days), + maxInitialTokens: 200 ether, + maxOngoingTokensPerSecond: 2 ether, + minSecondsPerCollection: 600, + maxSecondsPerCollection: 3600, + conditions: 0, + nonce: 1, + metadata: "" + }); + vm.prank(address(approver)); + IAgreementCollector.AgreementDetails memory updateDetails = _recurringCollector.offer( + OFFER_TYPE_UPDATE, + abi.encode(rcau), + 0 + ); + bytes32 rcauHash = updateDetails.versionHash; + + // Cancel the RCA offer first — pending RCAU survives independently + vm.expectEmit(true, true, false, true); + emit IRecurringCollector.OfferCancelled(address(approver), agreementId, rcaHash); + vm.prank(address(approver)); + _recurringCollector.cancel(agreementId, rcaHash, SCOPE_PENDING); + + IRecurringCollector.AgreementData memory after1 = _recurringCollector.getAgreement(agreementId); + assertEq(after1.activeTermsHash, bytes32(0), "active should be cleared"); + assertEq(after1.pendingTermsHash, rcauHash, "pending RCAU should survive RCA cancel"); + assertEq(after1.payer, address(approver), "agreement.payer persists for subsequent auth"); + + // Now cancel the pending RCAU — payer auth still works via persistent agreement.payer + vm.expectEmit(true, true, false, true); + emit IRecurringCollector.OfferCancelled(address(approver), agreementId, rcauHash); + vm.prank(address(approver)); + _recurringCollector.cancel(agreementId, rcauHash, SCOPE_PENDING); + + (uint8 activeType, ) = _recurringCollector.getAgreementOfferAt(agreementId, 0); + assertEq(activeType, 0, "Active offer should be gone"); + (uint8 pendingType, ) = _recurringCollector.getAgreementOfferAt(agreementId, 1); + assertEq(pendingType, 0, "Pending offer should be gone"); + } + // ══════════════════════════════════════════════════════════════════════ // Gap 16 — cancel: silent no-op when agreement not found // ══════════════════════════════════════════════════════════════════════ diff --git a/packages/horizon/test/unit/payments/recurring-collector/getMaxNextClaim.t.sol b/packages/horizon/test/unit/payments/recurring-collector/getMaxNextClaim.t.sol index 58aa6961d..fb46ba2dc 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/getMaxNextClaim.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/getMaxNextClaim.t.sol @@ -505,5 +505,189 @@ contract RecurringCollectorGetMaxNextClaimTest is RecurringCollectorSharedTest { assertLt(windowSeconds, maxSecondsPerCollection, "Window should be smaller than maxSecondsPerCollection"); } + /// @notice Symmetry of the pending-deadline fix for the pre-acceptance active branch. + /// An agreement that has been offered but not yet accepted (state == NotAccepted, but + /// activeTermsHash set) is admissible for acceptance at exactly `terms.deadline` because + /// accept() gates on `block.timestamp <= rca.deadline`. RAM's reservation envelope must + /// therefore still cover the potential claim window at that block. One second past, accept() + /// would revert and the agreement is unreachable, so max-claim drops to zero. + function test_GetMaxNextClaim_PreAcceptanceActiveAtExactDeadline_StillCounts() public { + MockAgreementOwner approver = new MockAgreementOwner(); + + // Build RCA manually so we control the exact deadline. + uint64 rcaDeadline = uint64(block.timestamp + 1 hours); + IRecurringCollector.RecurringCollectionAgreement memory rca = IRecurringCollector.RecurringCollectionAgreement({ + deadline: rcaDeadline, + endsAt: uint64(block.timestamp + 365 days), + payer: address(approver), + dataService: makeAddr("ds"), + serviceProvider: makeAddr("sp"), + maxInitialTokens: 100 ether, + maxOngoingTokensPerSecond: 1 ether, + minSecondsPerCollection: 600, + maxSecondsPerCollection: 3600, + conditions: 0, + nonce: 1, + metadata: "" + }); + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_NEW, abi.encode(rca), 0); + + bytes16 agreementId = _recurringCollector.generateAgreementId( + rca.payer, + rca.dataService, + rca.serviceProvider, + rca.deadline, + rca.nonce + ); + // Agreement is in NotAccepted state — activeTermsHash is set (by offer) but no accept() yet. + assertEq( + uint8(_recurringCollector.getAgreement(agreementId).state), + uint8(IRecurringCollector.AgreementState.NotAccepted), + "precondition: NotAccepted" + ); + + // One second before the deadline: pre-acceptance active counts. + vm.warp(uint256(rcaDeadline) - 1); + assertGt(_recurringCollector.getMaxNextClaim(agreementId, 1), 0, "active counts before deadline"); + + // At the exact deadline: accept() is still admissible (<=), so the pre-acceptance window + // must still count in the reservation envelope. + vm.warp(uint256(rcaDeadline)); + assertGt(_recurringCollector.getMaxNextClaim(agreementId, 1), 0, "active should still count at exact deadline"); + + // One second past the deadline: accept() would revert, so max-claim drops to zero. + vm.warp(uint256(rcaDeadline) + 1); + assertEq(_recurringCollector.getMaxNextClaim(agreementId, 1), 0, "active zero one second past deadline"); + } + + /// @notice Boundary: the guard uses `block.timestamp <= terms.deadline` (inclusive) to match + /// {update}'s admissibility — at the exact deadline block, update() can still promote the + /// pending to active, so RAM must keep reserving for it. One second past the deadline, the + /// pending is no longer admissible and drops to zero. + function test_GetMaxNextClaim_PendingAtExactDeadline_StillCounts() public { + MockAgreementOwner approver = new MockAgreementOwner(); + IRecurringCollector.RecurringCollectionAgreement memory rca = _recurringCollectorHelper.sensibleRCA( + IRecurringCollector.RecurringCollectionAgreement({ + deadline: uint64(block.timestamp + 1 hours), + endsAt: uint64(block.timestamp + 365 days), + payer: address(approver), + dataService: makeAddr("ds"), + serviceProvider: makeAddr("sp"), + maxInitialTokens: 100 ether, + maxOngoingTokensPerSecond: 1 ether, + minSecondsPerCollection: 600, + maxSecondsPerCollection: 3600, + conditions: 0, + nonce: 1, + metadata: "" + }) + ); + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_NEW, abi.encode(rca), 0); + _setupValidProvision(rca.serviceProvider, rca.dataService); + vm.prank(rca.dataService); + bytes16 agreementId = _recurringCollector.accept(rca, ""); + + // Build RCAU manually (not via sensibleRCAU, which overrides deadline to a tight window) + // so we can pick a deadline we control and warp exactly to its boundary. + uint64 pendingDeadline = uint64(block.timestamp + 1 hours); + IRecurringCollector.RecurringCollectionAgreementUpdate memory rcau = IRecurringCollector + .RecurringCollectionAgreementUpdate({ + agreementId: agreementId, + deadline: pendingDeadline, + endsAt: uint64(block.timestamp + 730 days), + maxInitialTokens: 200 ether, + maxOngoingTokensPerSecond: 10 ether, + minSecondsPerCollection: 600, + maxSecondsPerCollection: 7200, + conditions: 0, + nonce: 1, + metadata: "" + }); + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_UPDATE, abi.encode(rcau), 0); + + // One second before the deadline: pending counts. + vm.warp(uint256(pendingDeadline) - 1); + assertGt(_recurringCollector.getMaxNextClaim(agreementId, 2), 0, "pending counts before deadline"); + + // At the exact deadline: guard is inclusive `<=`, matching update()'s admissibility. + // update() can still promote the pending to active on this block, so RAM must keep it + // in the reservation envelope. + vm.warp(uint256(pendingDeadline)); + assertGt(_recurringCollector.getMaxNextClaim(agreementId, 2), 0, "pending counts at exact deadline"); + + // One second past the deadline: update() would revert, so pending drops to zero. + vm.warp(uint256(pendingDeadline) + 1); + assertEq(_recurringCollector.getMaxNextClaim(agreementId, 2), 0, "pending zero one second past deadline"); + } + + /// @notice An expired pending offer (deadline in the past, endsAt still in the future) must not + /// contribute to max-claim. {update} rejects past-deadline RCAUs so the pending can never be + /// promoted to active; counting it would over-reserve escrow in RAM. + function test_GetMaxNextClaim_PendingIgnored_AfterDeadline() public { + MockAgreementOwner approver = new MockAgreementOwner(); + IRecurringCollector.RecurringCollectionAgreement memory rca = _recurringCollectorHelper.sensibleRCA( + IRecurringCollector.RecurringCollectionAgreement({ + deadline: uint64(block.timestamp + 1 hours), + endsAt: uint64(block.timestamp + 365 days), + payer: address(approver), + dataService: makeAddr("ds"), + serviceProvider: makeAddr("sp"), + maxInitialTokens: 100 ether, + maxOngoingTokensPerSecond: 1 ether, + minSecondsPerCollection: 600, + maxSecondsPerCollection: 3600, + conditions: 0, + nonce: 1, + metadata: "" + }) + ); + + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_NEW, abi.encode(rca), 0); + _setupValidProvision(rca.serviceProvider, rca.dataService); + vm.prank(rca.dataService); + bytes16 agreementId = _recurringCollector.accept(rca, ""); + + // Pending RCAU with higher rate + short acceptance deadline but long endsAt. Build manually + // so we control the deadline exactly (sensibleRCAU would override it to a bounded window). + uint64 pendingDeadline = uint64(block.timestamp + 1 hours); + IRecurringCollector.RecurringCollectionAgreementUpdate memory rcau = IRecurringCollector + .RecurringCollectionAgreementUpdate({ + agreementId: agreementId, + deadline: pendingDeadline, + endsAt: uint64(block.timestamp + 730 days), + maxInitialTokens: 200 ether, + maxOngoingTokensPerSecond: 10 ether, + minSecondsPerCollection: 600, + maxSecondsPerCollection: 7200, + conditions: 0, + nonce: 1, + metadata: "" + }); + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_UPDATE, abi.encode(rcau), 0); + + uint256 activeClaim = _recurringCollector.getMaxNextClaim(agreementId, 1); // SCOPE_ACTIVE + + // Before deadline: higher-rate pending dominates the combined claim. + uint256 beforeDeadline = _recurringCollector.getMaxNextClaim(agreementId); + assertGt(beforeDeadline, activeClaim, "live pending dominates before its deadline"); + + // Warp one second past the pending's deadline. endsAt is still well in the future, so + // _maxClaimForTerms would still return a large number — but the pending can no longer + // be accepted via update(), so it must not contribute. + vm.warp(uint256(pendingDeadline) + 1); + + uint256 pendingScopeAfter = _recurringCollector.getMaxNextClaim(agreementId, 2); // SCOPE_PENDING + assertEq(pendingScopeAfter, 0, "expired pending returns 0 under SCOPE_PENDING"); + + uint256 combinedAfter = _recurringCollector.getMaxNextClaim(agreementId); + uint256 activeAfter = _recurringCollector.getMaxNextClaim(agreementId, 1); + assertEq(combinedAfter, activeAfter, "combined scope falls back to active-only after pending expires"); + } + /* solhint-enable graph/func-name-mixedcase */ } diff --git a/packages/horizon/test/unit/payments/recurring-collector/hashRoundTrip.t.sol b/packages/horizon/test/unit/payments/recurring-collector/hashRoundTrip.t.sol index 7c5c73cbe..cc75e78d9 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/hashRoundTrip.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/hashRoundTrip.t.sol @@ -177,8 +177,8 @@ contract RecurringCollectorHashRoundTripTest is RecurringCollectorSharedTest { IRecurringCollector.AgreementData memory agreement = _recurringCollector.getAgreement(agreementId); assertEq(agreement.activeTermsHash, rcauHash, "activeTermsHash should be RCAU hash after update"); - // Stored update offer persists after update - _verifyOfferRoundTrip(agreementId, 1, rcauHash); + // After update, RCAU becomes the active version (VERSION_CURRENT = 0) + _verifyOfferRoundTrip(agreementId, 0, rcauHash); } // ==================== Cancel pending, active stays ==================== diff --git a/packages/horizon/test/unit/payments/recurring-collector/mixedPath.t.sol b/packages/horizon/test/unit/payments/recurring-collector/mixedPath.t.sol index 120214815..659979dee 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/mixedPath.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/mixedPath.t.sol @@ -76,8 +76,7 @@ contract RecurringCollectorMixedPathTest is RecurringCollectorSharedTest { // Verify updated terms IRecurringCollector.AgreementData memory agreement = _recurringCollector.getAgreement(agreementId); - assertEq(agreement.maxOngoingTokensPerSecond, rcau.maxOngoingTokensPerSecond); - assertEq(agreement.maxSecondsPerCollection, rcau.maxSecondsPerCollection); + assertEq(agreement.activeTermsHash, _recurringCollector.hashRCAU(rcau)); assertEq(agreement.updateNonce, 1); } @@ -195,9 +194,87 @@ contract RecurringCollectorMixedPathTest is RecurringCollectorSharedTest { _recurringCollector.update(rcau, updateSig); IRecurringCollector.AgreementData memory agreement = _recurringCollector.getAgreement(agreementId); - assertEq(agreement.maxOngoingTokensPerSecond, rcau.maxOngoingTokensPerSecond); + assertEq(agreement.activeTermsHash, _recurringCollector.hashRCAU(rcau)); assertEq(agreement.updateNonce, 1); } + /// @notice Replacing the active offer preserves an independent pending RCAU. The update is + /// still a valid signed offer against the same agreementId; the payer may cancel it + /// explicitly if they don't want it. The contract shouldn't silently invalidate it. + function test_MixedPath_OfferNew_PreservesPendingRcau() public { + MockAgreementOwner approver = new MockAgreementOwner(); + + IRecurringCollector.RecurringCollectionAgreement memory rca1 = _recurringCollectorHelper.sensibleRCA( + IRecurringCollector.RecurringCollectionAgreement({ + deadline: 0, + endsAt: 0, + payer: address(approver), + dataService: makeAddr("ds"), + serviceProvider: makeAddr("sp"), + maxInitialTokens: 100 ether, + maxOngoingTokensPerSecond: 1 ether, + minSecondsPerCollection: 600, + maxSecondsPerCollection: 3600, + conditions: 0, + nonce: 1, + metadata: "" + }) + ); + // Derive the deterministic agreement ID from rca1's post-sensible fields. + bytes16 agreementId = _recurringCollector.generateAgreementId( + rca1.payer, + rca1.dataService, + rca1.serviceProvider, + rca1.deadline, + rca1.nonce + ); + + // Step 1: offer RCA → active = hashRCA(rca1) + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_NEW, abi.encode(rca1), 0); + bytes32 rca1Hash = _recurringCollector.hashRCA(rca1); + + // Step 2: offer RCAU → pending = hashRCAU(rcau) + IRecurringCollector.RecurringCollectionAgreementUpdate memory rcau = _recurringCollectorHelper.sensibleRCAU( + IRecurringCollector.RecurringCollectionAgreementUpdate({ + agreementId: agreementId, + deadline: 0, + endsAt: 0, + maxInitialTokens: 200 ether, + maxOngoingTokensPerSecond: 2 ether, + minSecondsPerCollection: 600, + maxSecondsPerCollection: 7200, + conditions: 0, + nonce: 1, + metadata: "" + }) + ); + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_UPDATE, abi.encode(rcau), 0); + bytes32 rcauHash = _recurringCollector.hashRCAU(rcau); + + // Pre-check: pending is set + IRecurringCollector.AgreementData memory before = _recurringCollector.getAgreement(agreementId); + assertEq(before.activeTermsHash, rca1Hash, "active should be rca1Hash after offer"); + assertEq(before.pendingTermsHash, rcauHash, "pending should be rcauHash after offer UPDATE"); + + // Step 3: offer different RCA with same primary fields (same agreementId, different terms) + IRecurringCollector.RecurringCollectionAgreement memory rca2 = rca1; + rca2.maxInitialTokens = 999 ether; // different terms → different hash, same agreementId + vm.prank(address(approver)); + _recurringCollector.offer(OFFER_TYPE_NEW, abi.encode(rca2), 0); + bytes32 rca2Hash = _recurringCollector.hashRCA(rca2); + + // Post-check: active replaced, pending preserved (still the original RCAU) + IRecurringCollector.AgreementData memory afterOffer = _recurringCollector.getAgreement(agreementId); + assertEq(afterOffer.activeTermsHash, rca2Hash, "active should be rca2Hash"); + assertEq(afterOffer.pendingTermsHash, rcauHash, "pending RCAU should still be queued"); + + // The pending offer's $.terms entry must still be retrievable — payer can still accept it + (uint8 pendingType, bytes memory pendingData) = _recurringCollector.getAgreementOfferAt(agreementId, 1); + assertEq(pendingType, OFFER_TYPE_UPDATE, "pending slot should still hold update offer"); + assertEq(keccak256(pendingData), keccak256(abi.encode(rcau)), "pending data should be the original RCAU"); + } + /* solhint-enable graph/func-name-mixedcase */ } diff --git a/packages/horizon/test/unit/payments/recurring-collector/offerStorageLifecycle.t.sol b/packages/horizon/test/unit/payments/recurring-collector/offerStorageLifecycle.t.sol index 0aece90ae..a4988b4b0 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/offerStorageLifecycle.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/offerStorageLifecycle.t.sol @@ -215,7 +215,7 @@ contract RecurringCollectorOfferStorageLifecycleTest is RecurringCollectorShared /// @notice Pre-acceptance cancel of the RCA under SCOPE_PENDING deletes BOTH the RCA offer /// and any pending RCAU offer. After cascade, both slots are empty. - function test_CancelPreAcceptanceRca_CascadesDeleteRcau() public { + function test_CancelPreAcceptanceRca_PreservesPendingRcau() public { MockAgreementOwner approver = new MockAgreementOwner(); IRecurringCollector.RecurringCollectionAgreement memory rca = _makeRca(address(approver)); bytes32 rcaHash = _recurringCollector.hashRCA(rca); @@ -239,7 +239,7 @@ contract RecurringCollectorOfferStorageLifecycleTest is RecurringCollectorShared assertEq(preCurrentType, OFFER_TYPE_NEW, "RCA stored before cancel"); assertEq(preNextType, OFFER_TYPE_UPDATE, "RCAU stored before cancel"); - // Cancel the pre-acceptance RCA — one OfferCancelled event, both slots cleared + // Cancel the pre-acceptance RCA — one OfferCancelled event; pending RCAU survives vm.expectEmit(address(_recurringCollector)); emit IRecurringCollector.OfferCancelled(address(approver), agreementId, rcaHash); vm.prank(address(approver)); @@ -247,7 +247,7 @@ contract RecurringCollectorOfferStorageLifecycleTest is RecurringCollectorShared IRecurringCollector.AgreementData memory agreement = _recurringCollector.getAgreement(agreementId); assertEq(agreement.activeTermsHash, bytes32(0), "activeTermsHash cleared"); - assertEq(agreement.pendingTermsHash, bytes32(0), "pendingTermsHash cascade-cleared"); + assertEq(agreement.pendingTermsHash, rcauHash, "pendingTermsHash survives RCA cancel"); (uint8 currentType, bytes memory currentData) = _recurringCollector.getAgreementOfferAt( agreementId, @@ -257,19 +257,14 @@ contract RecurringCollectorOfferStorageLifecycleTest is RecurringCollectorShared assertEq(currentData.length, 0, "RCA data empty"); (uint8 nextType, bytes memory nextData) = _recurringCollector.getAgreementOfferAt(agreementId, VERSION_NEXT); - assertEq(nextType, OFFER_TYPE_NONE, "RCAU offer cascade-deleted"); - assertEq(nextData.length, 0, "RCAU data empty"); - - // The original rcauHash stored-offer entry is no longer referenced. No version hash - // resolves to it — confirmed above — so the cleanup is complete for view purposes. - rcauHash; // silence unused warning; kept for clarity in the narrative + assertEq(nextType, OFFER_TYPE_UPDATE, "RCAU offer still retrievable"); + assertEq(keccak256(nextData), keccak256(abi.encode(rcau)), "RCAU data intact"); } - /// @notice After a pre-acceptance cascade delete, a follow-up cancel targeting the orphan RCAU - /// hash must NOT revert: _requirePayerIfExists short-circuits because agreement.payer was - /// zeroed when activeTermsHash was cleared — but the agreement struct still exists. The cancel - /// is therefore a no-op targeting already-empty state. - function test_CancelPreAcceptanceRca_SubsequentRcauCancel_DoesNotRevert() public { + /// @notice Pre-acceptance RCA and pending RCAU can be cancelled in either order — + /// agreement.payer is a persistent field, so cancelling one doesn't un-authorize cancelling + /// the other. + function test_CancelPreAcceptance_EitherOrder() public { MockAgreementOwner approver = new MockAgreementOwner(); IRecurringCollector.RecurringCollectionAgreement memory rca = _makeRca(address(approver)); bytes32 rcaHash = _recurringCollector.hashRCA(rca); @@ -287,15 +282,19 @@ contract RecurringCollectorOfferStorageLifecycleTest is RecurringCollectorShared vm.prank(address(approver)); _recurringCollector.offer(OFFER_TYPE_UPDATE, abi.encode(rcau), 0); - // Cancel the RCA — cascades the RCAU + // Cancel the RCA first vm.prank(address(approver)); _recurringCollector.cancel(agreementId, rcaHash, SCOPE_PENDING); - // The approver can still cancel(rcauHash) without reverting — the payer slot on the - // agreement is still set (clearing is by *termsHash*, not payer field), so the call - // enters the pending-hash branch, observes pendingTermsHash == 0, and exits silently. + // Then cancel the pending RCAU — must succeed because agreement.payer is persistent + vm.expectEmit(address(_recurringCollector)); + emit IRecurringCollector.OfferCancelled(address(approver), agreementId, rcauHash); vm.prank(address(approver)); _recurringCollector.cancel(agreementId, rcauHash, SCOPE_PENDING); + + IRecurringCollector.AgreementData memory agreement = _recurringCollector.getAgreement(agreementId); + assertEq(agreement.activeTermsHash, bytes32(0), "active cleared"); + assertEq(agreement.pendingTermsHash, bytes32(0), "pending cleared"); } /// @notice Pre-acceptance cancel with no pending RCAU still deletes the RCA offer and diff --git a/packages/horizon/test/unit/payments/recurring-collector/update.t.sol b/packages/horizon/test/unit/payments/recurring-collector/update.t.sol index 234e552d6..158157554 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/update.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/update.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.27; import { IRecurringCollector } from "@graphprotocol/interfaces/contracts/horizon/IRecurringCollector.sol"; +import { OFFER_TYPE_UPDATE } from "@graphprotocol/interfaces/contracts/horizon/IAgreementCollector.sol"; import { RecurringCollectorSharedTest } from "./shared.t.sol"; @@ -153,11 +154,7 @@ contract RecurringCollectorUpdateTest is RecurringCollectorSharedTest { _recurringCollector.update(rcau, signature); IRecurringCollector.AgreementData memory agreement = _recurringCollector.getAgreement(agreementId); - assertEq(rcau.endsAt, agreement.endsAt); - assertEq(rcau.maxInitialTokens, agreement.maxInitialTokens); - assertEq(rcau.maxOngoingTokensPerSecond, agreement.maxOngoingTokensPerSecond); - assertEq(rcau.minSecondsPerCollection, agreement.minSecondsPerCollection); - assertEq(rcau.maxSecondsPerCollection, agreement.maxSecondsPerCollection); + assertEq(agreement.activeTermsHash, _recurringCollector.hashRCAU(rcau)); assertEq(rcau.nonce, agreement.updateNonce); } @@ -346,5 +343,51 @@ contract RecurringCollectorUpdateTest is RecurringCollectorSharedTest { assertEq(afterSecond.activeTermsHash, afterFirst.activeTermsHash, "activeTermsHash unchanged"); } + /// @notice Direct-apply update (no prior offer(UPDATE) that staged the RCAU as pending) writes + /// new terms via _validateAndStoreTerms, which must emit OfferStored. AgreementUpdated follows. + function test_Update_EmitsOfferStored_WhenDirectApplyFreshTerms(FuzzyTestUpdate calldata fuzzyTestUpdate) public { + ( + IRecurringCollector.RecurringCollectionAgreement memory acceptedRca, + , + uint256 signerKey, + bytes16 agreementId + ) = _sensibleAuthorizeAndAccept(fuzzyTestUpdate.fuzzyTestAccept); + + IRecurringCollector.RecurringCollectionAgreementUpdate memory rcau = _recurringCollectorHelper.sensibleRCAU( + fuzzyTestUpdate.rcau + ); + rcau.agreementId = agreementId; + + ( + IRecurringCollector.RecurringCollectionAgreementUpdate memory signedRcau, + bytes memory signature + ) = _recurringCollectorHelper.generateSignedRCAUForAgreement(agreementId, rcau, signerKey); + bytes32 rcauHash = _recurringCollector.hashRCAU(signedRcau); + + // Pre-condition: no pending offer staged, so update() takes the direct-apply branch. + assertEq( + _recurringCollector.getAgreement(agreementId).pendingTermsHash, + bytes32(0), + "no pending before direct-apply" + ); + + vm.expectEmit(address(_recurringCollector)); + emit IRecurringCollector.OfferStored(agreementId, acceptedRca.payer, OFFER_TYPE_UPDATE, rcauHash); + vm.expectEmit(address(_recurringCollector)); + emit IRecurringCollector.AgreementUpdated( + acceptedRca.dataService, + acceptedRca.payer, + acceptedRca.serviceProvider, + agreementId, + signedRcau.endsAt, + signedRcau.maxInitialTokens, + signedRcau.maxOngoingTokensPerSecond, + signedRcau.minSecondsPerCollection, + signedRcau.maxSecondsPerCollection + ); + vm.prank(acceptedRca.dataService); + _recurringCollector.update(signedRcau, signature); + } + /* solhint-enable graph/func-name-mixedcase */ } diff --git a/packages/horizon/test/unit/payments/recurring-collector/updateUnsigned.t.sol b/packages/horizon/test/unit/payments/recurring-collector/updateUnsigned.t.sol index b0059e303..d91bb9a5c 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/updateUnsigned.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/updateUnsigned.t.sol @@ -98,11 +98,7 @@ contract RecurringCollectorUpdateUnsignedTest is RecurringCollectorSharedTest { _recurringCollector.update(rcau, ""); IRecurringCollector.AgreementData memory agreement = _recurringCollector.getAgreement(agreementId); - assertEq(rcau.endsAt, agreement.endsAt); - assertEq(rcau.maxInitialTokens, agreement.maxInitialTokens); - assertEq(rcau.maxOngoingTokensPerSecond, agreement.maxOngoingTokensPerSecond); - assertEq(rcau.minSecondsPerCollection, agreement.minSecondsPerCollection); - assertEq(rcau.maxSecondsPerCollection, agreement.maxSecondsPerCollection); + assertEq(agreement.activeTermsHash, _recurringCollector.hashRCAU(rcau)); assertEq(rcau.nonce, agreement.updateNonce); } diff --git a/packages/horizon/test/unit/payments/recurring-collector/upgradeScenario.t.sol b/packages/horizon/test/unit/payments/recurring-collector/upgradeScenario.t.sol index f65fe9464..82d2a1468 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/upgradeScenario.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/upgradeScenario.t.sol @@ -129,7 +129,7 @@ contract RecurringCollectorUpgradeScenarioTest is Test, Bounder { assertEq(v2Agreement.payer, payer, "payer lost"); assertEq(v2Agreement.serviceProvider, rca.serviceProvider, "serviceProvider lost"); assertEq(v2Agreement.dataService, rca.dataService, "dataService lost"); - assertEq(v2Agreement.maxOngoingTokensPerSecond, rca.maxOngoingTokensPerSecond, "terms lost"); + assertEq(v2Agreement.activeTermsHash, _recurringCollector.hashRCA(rca), "terms hash lost"); assertTrue(_recurringCollector.pauseGuardians(makeAddr("guardian")), "pause guardian lost"); } diff --git a/packages/horizon/test/unit/payments/recurring-collector/viewFunctions.t.sol b/packages/horizon/test/unit/payments/recurring-collector/viewFunctions.t.sol index 839cd146e..80445920b 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/viewFunctions.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/viewFunctions.t.sol @@ -15,10 +15,8 @@ contract RecurringCollectorViewFunctionsTest is RecurringCollectorSharedTest { function test_GetCollectionInfo_Accepted_AfterTime(FuzzyTestAccept calldata fuzzy) public { (, , , bytes16 agreementId) = _sensibleAuthorizeAndAccept(fuzzy); - IRecurringCollector.AgreementData memory agreement = _recurringCollector.getAgreement(agreementId); - - // Skip some time - skip(agreement.minSecondsPerCollection); + // Skip past the minimum collection window so collection is possible + skip(_recurringCollector.MIN_SECONDS_COLLECTION_WINDOW()); // Re-read agreement (timestamps don't change but view computes based on block.timestamp) (bool isCollectable, uint256 collectionSeconds, ) = _recurringCollector.getCollectionInfo(agreementId); @@ -129,22 +127,13 @@ contract RecurringCollectorViewFunctionsTest is RecurringCollectorSharedTest { assertEq(agreement.payer, rca.payer, "payer should match"); assertEq(agreement.dataService, rca.dataService, "dataService should match"); assertEq(agreement.serviceProvider, rca.serviceProvider, "serviceProvider should match"); - assertEq(agreement.endsAt, rca.endsAt, "endsAt should match"); - assertEq(agreement.minSecondsPerCollection, rca.minSecondsPerCollection, "minSeconds should match"); - assertEq(agreement.maxSecondsPerCollection, rca.maxSecondsPerCollection, "maxSeconds should match"); - assertEq(agreement.maxInitialTokens, rca.maxInitialTokens, "maxInitialTokens should match"); - assertEq( - agreement.maxOngoingTokensPerSecond, - rca.maxOngoingTokensPerSecond, - "maxOngoingTokensPerSecond should match" - ); assertEq( uint8(agreement.state), uint8(IRecurringCollector.AgreementState.Accepted), "state should be Accepted" ); assertTrue(agreement.acceptedAt > 0, "acceptedAt should be set"); - assertTrue(agreement.activeTermsHash != bytes32(0), "activeTermsHash should be set"); + assertEq(agreement.activeTermsHash, _recurringCollector.hashRCA(rca), "activeTermsHash should match RCA hash"); } /* solhint-enable graph/func-name-mixedcase */ diff --git a/packages/interfaces/contracts/horizon/IRecurringCollector.sol b/packages/interfaces/contracts/horizon/IRecurringCollector.sol index 74ccde753..052c7850d 100644 --- a/packages/interfaces/contracts/horizon/IRecurringCollector.sol +++ b/packages/interfaces/contracts/horizon/IRecurringCollector.sol @@ -103,41 +103,31 @@ interface IRecurringCollector is IAuthorizable, IAgreementCollector { /** * @notice The data for an agreement * @dev This struct is used to store the data of an agreement in the contract. - * Fields are ordered for optimal storage packing (7 slots). + * Fields are ordered for optimal storage packing (5 slots). * @param dataService The address of the data service * @param acceptedAt The timestamp when the agreement was accepted - * @param minSecondsPerCollection The minimum amount of seconds that must pass between collections + * @param updateNonce The current nonce for updates (prevents replay attacks) * @param payer The address of the payer * @param lastCollectionAt The timestamp when the agreement was last collected at - * @param maxSecondsPerCollection The maximum seconds of service that can be collected in a single collection * @param serviceProvider The address of the service provider - * @param endsAt The timestamp when the agreement ends - * @param updateNonce The current nonce for updates (prevents replay attacks) - * @param maxInitialTokens The maximum amount of tokens that can be collected in the first collection - * on top of the amount allowed for subsequent collections - * @param maxOngoingTokensPerSecond The maximum amount of tokens that can be collected per second - * except for the first collection - * @param activeTermsHash EIP-712 hash of the currently active terms (RCA or RCAU) * @param canceledAt The timestamp when the agreement was canceled - * @param conditions Bitmask of payer-declared conditions * @param state The state of the agreement + * @param activeTermsHash EIP-712 hash of the current version. For accepted agreements this + * is the active terms (RCA or RCAU) hash. For pre-acceptance agreements this is the stored + * RCA offer hash. Use `state` to distinguish accepted from pre-acceptance. + * @param pendingTermsHash EIP-712 hash of the queued pending update (RCAU), or 0 if none. */ struct AgreementData { - address dataService; // 20 bytes ─┐ slot 0 (32/32) - uint64 acceptedAt; // 8 bytes ─┤ - uint32 minSecondsPerCollection; // 4 bytes ─┘ - address payer; // 20 bytes ─┐ slot 1 (32/32) - uint64 lastCollectionAt; // 8 bytes ─┤ - uint32 maxSecondsPerCollection; // 4 bytes ─┘ - address serviceProvider; // 20 bytes ─┐ slot 2 (32/32) - uint64 endsAt; // 8 bytes ─┤ - uint32 updateNonce; // 4 bytes ─┘ - uint256 maxInitialTokens; // 32 bytes ─── slot 3 - uint256 maxOngoingTokensPerSecond; // 32 bytes ─── slot 4 - bytes32 activeTermsHash; // 32 bytes ─── slot 5 - uint64 canceledAt; // 8 bytes ─┐ slot 6 (11/32) - uint16 conditions; // 2 bytes ─┤ - AgreementState state; // 1 byte ─┘ + address dataService; // 20 bytes ─┐ slot 0 (32/32) + uint64 acceptedAt; // 8 bytes ─┤ + uint32 updateNonce; // 4 bytes ─┘ + address payer; // 20 bytes ─┐ slot 1 (28/32) + uint64 lastCollectionAt; // 8 bytes ─┘ + address serviceProvider; // 20 bytes ─┐ slot 2 (29/32) + uint64 canceledAt; // 8 bytes ─┤ + AgreementState state; // 1 byte ─┘ + bytes32 activeTermsHash; // 32 bytes ── slot 3 + bytes32 pendingTermsHash; // 32 bytes ── slot 4 } /** @@ -248,11 +238,6 @@ interface IRecurringCollector is IAuthorizable, IAgreementCollector { */ error RecurringCollectorAgreementNotFound(bytes16 agreementId); - /** - * @notice Thrown when accepting an agreement with a zero ID - */ - error RecurringCollectorAgreementIdZero(); - /** * @notice Thrown when interacting with an agreement not owned by the message sender * @param agreementId The agreement ID @@ -316,14 +301,14 @@ interface IRecurringCollector is IAuthorizable, IAgreementCollector { error RecurringCollectorAgreementAddressNotSet(); /** - * @notice Thrown when an agreement's endsAt is not strictly after its acceptance deadline. - * @param deadline The offer acceptance deadline + * @notice Thrown when an agreement's endsAt is not strictly after its deadline + * @param deadline The offer's acceptance deadline * @param endsAt The agreement end timestamp */ error RecurringCollectorAgreementEndsBeforeDeadline(uint64 deadline, uint64 endsAt); /** - * @notice Thrown when accepting or upgrading an agreement with an elapsed endsAt + * @notice Thrown when an agreement's collection window is below the minimum * @param allowedMinCollectionWindow The allowed minimum collection window * @param minSecondsPerCollection The minimum seconds per collection * @param maxSecondsPerCollection The maximum seconds per collection @@ -444,6 +429,14 @@ interface IRecurringCollector is IAuthorizable, IAgreementCollector { */ event OfferStored(bytes16 indexed agreementId, address indexed payer, uint8 indexed offerType, bytes32 offerHash); + /** + * @notice Emitted when an offer is cancelled via SCOPE_SIGNED or SCOPE_PENDING + * @param caller The address that cancelled the offer (msg.sender) + * @param agreementId The agreement ID + * @param hash The EIP-712 hash that was cancelled + */ + event OfferCancelled(address indexed caller, bytes16 indexed agreementId, bytes32 hash); + /** * @notice Pauses the collector, blocking accept, update, collect, and cancel. * @dev Only callable by a pause guardian. Uses OpenZeppelin Pausable. diff --git a/packages/issuance/audits/PR1301/TRST-L-7.md b/packages/issuance/audits/PR1301/TRST-L-7.md index 1eee39005..5976cd463 100644 --- a/packages/issuance/audits/PR1301/TRST-L-7.md +++ b/packages/issuance/audits/PR1301/TRST-L-7.md @@ -20,3 +20,14 @@ Extend `_requirePayer()` to also check `rcauOffers` for a payer match when neith TBD --- + +Resolved by restructuring offer storage so cancellation authorization no longer depends on +the stored RCA offer. In the current design, `agreement.payer` is a persistent field set at +first registration (via `_registerAgreement`) and is not cleared on cancel. The authorization +helper (`_requirePayerIfExists`) reads `agreement.payer` directly rather than falling back +through stored offer entries. + +As a consequence, cancelling a pre-acceptance RCA offer and cancelling a pending RCAU offer +are fully independent operations that may be performed in either order. Neither path leaves +the other unreachable, because the persistent `agreement.payer` continues to authorize the +surviving offer. diff --git a/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/accept.t.sol b/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/accept.t.sol index b2853949d..77be7c67d 100644 --- a/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/accept.t.sol +++ b/packages/subgraph-service/test/unit/subgraphService/indexing-agreement/accept.t.sol @@ -473,5 +473,63 @@ contract SubgraphServiceIndexingAgreementAcceptTest is SubgraphServiceIndexingAg ); assertEq(afterOldClose.agreement.allocationId, secondAllocationId, "still bound to second allocation"); } + + /// @notice Rebinding an already-accepted agreement to a new allocation must still succeed after + /// the original RCA's acceptance deadline has elapsed. The collector's idempotent short-circuit + /// runs before the deadline check — same-hash re-accept is a no-op and does not consume the + /// signature's lifetime. Without this, indexers could not move agreements across allocations + /// after the typically-short RCA acceptance window closes. + function test_SubgraphService_AcceptIndexingAgreement_Rebinds_AfterRcaDeadline( + Seed memory seed, + uint256 secondAllocationKey + ) public { + Context storage ctx = _newCtx(seed); + IndexerState memory indexerState = _withIndexer(ctx); + ( + IRecurringCollector.RecurringCollectionAgreement memory acceptedRca, + bytes16 agreementId + ) = _withAcceptedIndexingAgreement(ctx, indexerState); + + // Top up provision and allocate a second allocation on the same subgraph deployment. + uint256 extraTokens = 10_000_000 ether; + deal({ token: address(token), to: indexerState.addr, give: extraTokens }); + resetPrank(indexerState.addr); + _addToProvision(indexerState.addr, extraTokens); + + secondAllocationKey = boundKey(secondAllocationKey); + address secondAllocationId = vm.addr(secondAllocationKey); + vm.assume(secondAllocationId != indexerState.allocationId); + vm.assume(ctx.allocations[secondAllocationId] == address(0)); + ctx.allocations[secondAllocationId] = indexerState.addr; + + bytes memory allocData = _createSubgraphAllocationData( + indexerState.addr, + indexerState.subgraphDeploymentId, + secondAllocationKey, + extraTokens + ); + _startService(indexerState.addr, allocData); + + // Warp past the RCA's acceptance deadline. A fresh accept would now revert with + // RecurringCollectorAgreementDeadlineElapsed — the rebind must take the idempotent path. + vm.warp(uint256(acceptedRca.deadline) + 1); + + (, bytes memory signature) = _recurringCollectorHelper.generateSignedRCA( + acceptedRca, + ctx.payer.signerPrivateKey + ); + + resetPrank(indexerState.addr); + bytes16 returnedId = subgraphService.acceptIndexingAgreement(secondAllocationId, acceptedRca, signature); + assertEq(returnedId, agreementId, "rebind after deadline returns same agreementId"); + + IIndexingAgreement.AgreementWrapper memory rebound = subgraphService.getIndexingAgreement(agreementId); + assertEq(rebound.agreement.allocationId, secondAllocationId, "rebound to second allocation after deadline"); + assertEq( + uint8(rebound.collectorAgreement.state), + uint8(IRecurringCollector.AgreementState.Accepted), + "collector state still Accepted after post-deadline rebind" + ); + } /* solhint-enable graph/func-name-mixedcase */ } diff --git a/packages/testing/test/integration/AgreementLifecycleAdvanced.t.sol b/packages/testing/test/integration/AgreementLifecycleAdvanced.t.sol index d20a8e347..95f91f1a0 100644 --- a/packages/testing/test/integration/AgreementLifecycleAdvanced.t.sol +++ b/packages/testing/test/integration/AgreementLifecycleAdvanced.t.sol @@ -600,11 +600,93 @@ contract AgreementLifecycleAdvancedTest is FullStackHarness { ); } + // ═══════════════════════════════════════════════════════════════════ + // Scenario 15: Rebind after cancellation — collector state authority + // ═══════════════════════════════════════════════════════════════════ + + /// @notice Cancellation is terminal at the collector. The SubgraphService rebind path must + /// defer to that authority: an attempt to rebind a cancelled agreement onto a fresh allocation + /// must revert, leaving both the collector state and SS state untouched. Exercises the full + /// offer → accept → cancel → open-second-allocation → rebind-attempt flow end-to-end with the + /// real contract stack. + function test_Scenario15_RebindAfterCancellation_Reverts() public { + IndexingAgreement.IndexingAgreementTermsV1 memory terms = IndexingAgreement.IndexingAgreementTermsV1({ + tokensPerSecond: 0.5 ether, + tokensPerEntityPerSecond: 0 + }); + IRecurringCollector.RecurringCollectionAgreement memory rca = _buildRCA(indexer, 0, 1 ether, 3600, terms); + bytes16 agreementId = _offerAndAccept(indexer, rca); + + // Cancel via the indexer path — CanceledByServiceProvider. + vm.prank(indexer.addr); + subgraphService.cancelIndexingAgreement(indexer.addr, agreementId); + assertEq( + uint8(recurringCollector.getAgreement(agreementId).state), + uint8(IRecurringCollector.AgreementState.CanceledByServiceProvider), + "precondition: cancelled at collector" + ); + + // Open a second allocation on the same subgraph deployment. + (address secondAllocationId, address cancelRebindTarget) = _openSecondAllocationForIndexer( + indexer, + "cancel-rebind-alloc" + ); + assertEq(cancelRebindTarget, indexer.addr, "indexer owns the new allocation"); + + // Attempt rebind to the new allocation. SS would stage the bookkeeping, but collector + // rejects (state != NotAccepted), reverting the whole tx. Both layers stay clean. + vm.prank(indexer.addr); + vm.expectRevert( + abi.encodeWithSelector( + IRecurringCollector.RecurringCollectorAgreementIncorrectState.selector, + agreementId, + IRecurringCollector.AgreementState.CanceledByServiceProvider + ) + ); + subgraphService.acceptIndexingAgreement(secondAllocationId, rca, ""); + + // Post-revert: agreement still cancelled at collector, still bound to old allocation in SS. + assertEq( + uint8(recurringCollector.getAgreement(agreementId).state), + uint8(IRecurringCollector.AgreementState.CanceledByServiceProvider), + "collector state unchanged" + ); + IIndexingAgreement.AgreementWrapper memory wrapper = subgraphService.getIndexingAgreement(agreementId); + assertEq(wrapper.agreement.allocationId, indexer.allocationId, "SS still bound to original allocation"); + } + // ── Helpers ── function _getHardcodedPoiMetadata() internal view returns (bytes memory) { return abi.encode(block.number, bytes32("PUBLIC_POI1"), uint8(0), uint8(0), uint256(0)); } + + /// @notice Top up the indexer's provision and open a second allocation on the same + /// subgraph deployment. Returns the new allocation's id plus the indexer that owns it + /// (both for readability and to let callers assert ownership in a single expression). + function _openSecondAllocationForIndexer( + IndexerSetup memory _indexer, + string memory _label + ) internal returns (address allocationId, address owner) { + uint256 extraTokens = MINIMUM_PROVISION_TOKENS; + _addProvisionTokens(_indexer, extraTokens); + + uint256 allocationKey; + (allocationId, allocationKey) = makeAddrAndKey(_label); + + bytes32 digest = subgraphService.encodeAllocationProof(_indexer.addr, allocationId); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(allocationKey, digest); + bytes memory allocationData = abi.encode( + _indexer.subgraphDeploymentId, + extraTokens, + allocationId, + abi.encodePacked(r, s, v) + ); + vm.prank(_indexer.addr); + subgraphService.startService(_indexer.addr, allocationData); + + owner = _indexer.addr; + } } /// @notice Mock eligibility oracle for testing From 03038e1b181f0d4b738f71421e444d6295eb5f52 Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Sun, 19 Apr 2026 19:56:31 +0000 Subject: [PATCH 29/31] feat(collector): compose cancel/settled flags in getAgreementDetails (TRST-R-12) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Populate state flags beyond REGISTERED/ACCEPTED/UPDATE so agreement-scoped views distinguish cancelled from live and signal when nothing is currently claimable: - NOTICE_GIVEN + BY_PAYER / BY_PROVIDER — cancelled agreement, origin identified by the BY_* flag. - SETTLED — _getMaxNextClaimScoped(agreementId, 0) returns zero, meaning no tokens are claimable under either active or pending scope. Covers provider-cancelled agreements (immediately non-collectable), fully-collected agreements, and payer-cancelled agreements past their canceledAt window. --- .../collectors/RecurringCollector.sol | 10 +++ .../getAgreementDetails.t.sol | 87 ++++++++++++++++++- packages/issuance/audits/PR1301/TRST-R-11.md | 2 + packages/issuance/audits/PR1301/TRST-R-12.md | 10 +++ 4 files changed, 108 insertions(+), 1 deletion(-) diff --git a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol index cfb6dbb93..3af28e433 100644 --- a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol +++ b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol @@ -22,6 +22,10 @@ import { OFFER_TYPE_UPDATE, ACCEPTED, REGISTERED, + NOTICE_GIVEN, + SETTLED, + BY_PAYER, + BY_PROVIDER, UPDATE, VERSION_CURRENT, VERSION_NEXT, @@ -564,6 +568,12 @@ contract RecurringCollector is details.state |= ACCEPTED; if (offerType == OFFER_TYPE_UPDATE) details.state |= UPDATE; + + if (agreementState == AgreementState.CanceledByPayer) details.state |= NOTICE_GIVEN | BY_PAYER; + else if (agreementState == AgreementState.CanceledByServiceProvider) + details.state |= NOTICE_GIVEN | BY_PROVIDER; + + if (_getMaxNextClaimScoped(agreementId, 0) == 0) details.state |= SETTLED; } /** diff --git a/packages/horizon/test/unit/payments/recurring-collector/getAgreementDetails.t.sol b/packages/horizon/test/unit/payments/recurring-collector/getAgreementDetails.t.sol index 91d788020..42c847394 100644 --- a/packages/horizon/test/unit/payments/recurring-collector/getAgreementDetails.t.sol +++ b/packages/horizon/test/unit/payments/recurring-collector/getAgreementDetails.t.sol @@ -5,7 +5,13 @@ import { IRecurringCollector } from "@graphprotocol/interfaces/contracts/horizon import { IAgreementCollector, OFFER_TYPE_NEW, - REGISTERED + REGISTERED, + ACCEPTED, + NOTICE_GIVEN, + SETTLED, + BY_PAYER, + BY_PROVIDER, + VERSION_CURRENT } from "@graphprotocol/interfaces/contracts/horizon/IAgreementCollector.sol"; import { RecurringCollectorSharedTest } from "./shared.t.sol"; @@ -107,4 +113,83 @@ contract RecurringCollectorGetAgreementDetailsTest is RecurringCollectorSharedTe assertEq(details.serviceProvider, rca.serviceProvider); assertNotEq(details.versionHash, bytes32(0)); } + + // -- Cancel sets NOTICE_GIVEN + origin flag; provider cancel is always SETTLED -- + + function test_GetAgreementDetails_CanceledByServiceProvider_Flags(FuzzyTestAccept calldata fuzzyTestAccept) public { + ( + IRecurringCollector.RecurringCollectionAgreement memory rca, + , + , + bytes16 agreementId + ) = _sensibleAuthorizeAndAccept(fuzzyTestAccept); + + vm.prank(rca.dataService); + _recurringCollector.cancel(agreementId, IRecurringCollector.CancelAgreementBy.ServiceProvider); + + IAgreementCollector.AgreementDetails memory details = _recurringCollector.getAgreementDetails( + agreementId, + VERSION_CURRENT + ); + + assertEq( + details.state, + REGISTERED | ACCEPTED | NOTICE_GIVEN | BY_PROVIDER | SETTLED, + "provider cancel: REGISTERED|ACCEPTED|NOTICE_GIVEN|BY_PROVIDER|SETTLED" + ); + } + + function test_GetAgreementDetails_CanceledByPayer_Flags(FuzzyTestAccept calldata fuzzyTestAccept) public { + ( + IRecurringCollector.RecurringCollectionAgreement memory rca, + , + , + bytes16 agreementId + ) = _sensibleAuthorizeAndAccept(fuzzyTestAccept); + + vm.prank(rca.dataService); + _recurringCollector.cancel(agreementId, IRecurringCollector.CancelAgreementBy.Payer); + + IAgreementCollector.AgreementDetails memory details = _recurringCollector.getAgreementDetails( + agreementId, + VERSION_CURRENT + ); + + uint16 baseline = REGISTERED | ACCEPTED | NOTICE_GIVEN | BY_PAYER; + assertTrue( + details.state == baseline || details.state == (baseline | SETTLED), + "payer cancel: REGISTERED|ACCEPTED|NOTICE_GIVEN|BY_PAYER (+SETTLED if fully elapsed)" + ); + assertEq(details.state & NOTICE_GIVEN, NOTICE_GIVEN, "NOTICE_GIVEN set"); + assertEq(details.state & BY_PAYER, BY_PAYER, "BY_PAYER set"); + assertEq(details.state & BY_PROVIDER, 0, "BY_PROVIDER not set"); + } + + // -- Accepted agreement with nothing left to claim reports SETTLED -- + + function test_GetAgreementDetails_Accepted_ElapsedSetsSettled(FuzzyTestAccept calldata fuzzyTestAccept) public { + ( + IRecurringCollector.RecurringCollectionAgreement memory rca, + , + , + bytes16 agreementId + ) = _sensibleAuthorizeAndAccept(fuzzyTestAccept); + + // Jump past the agreement's end so no further collection is possible once lastCollectionAt + // catches up. Without any collections, _getMaxNextClaim still returns a non-zero value + // (late-collection semantics), so the clearest SETTLED case is via provider cancel — but + // we want to assert the non-cancel path here too. Simulate fully-collected state by + // advancing to endsAt + 1 and marking lastCollectionAt == endsAt via a well-formed path: + // easiest is a payer cancel far in the past (canceledAt in the past → window empty). + vm.prank(rca.dataService); + _recurringCollector.cancel(agreementId, IRecurringCollector.CancelAgreementBy.Payer); + vm.warp(rca.endsAt + 1); + + IAgreementCollector.AgreementDetails memory details = _recurringCollector.getAgreementDetails( + agreementId, + VERSION_CURRENT + ); + + assertEq(details.state & SETTLED, SETTLED, "SETTLED set when nothing left to claim"); + } } diff --git a/packages/issuance/audits/PR1301/TRST-R-11.md b/packages/issuance/audits/PR1301/TRST-R-11.md index ae93fd58d..d200e7468 100644 --- a/packages/issuance/audits/PR1301/TRST-R-11.md +++ b/packages/issuance/audits/PR1301/TRST-R-11.md @@ -11,3 +11,5 @@ Removed usused flags: `AUTO_UPDATE`, `AUTO_UPDATED`, `BY_DATA_SERVICE`, `WITH_NOTICE` and `IF_NOT_ACCEPTED` are dropped from the interface. NatSpec updated for remaining flags with new semantics. + +In RecurringCollector `NOTICE_GIVEN`, `SETTLED`, `BY_PAYER`, `BY_PROVIDER` are now set by `getAgreementDetails` to describe cancel origin and collectability (see TRST-R-12 fix). diff --git a/packages/issuance/audits/PR1301/TRST-R-12.md b/packages/issuance/audits/PR1301/TRST-R-12.md index a73ed9648..030c0272d 100644 --- a/packages/issuance/audits/PR1301/TRST-R-12.md +++ b/packages/issuance/audits/PR1301/TRST-R-12.md @@ -5,3 +5,13 @@ ## Description In `getAgreementDetails()`, any agreement whose state is not `AgreementState.NotAccepted` is reported with state flag `ACCEPTED`. This includes agreements that have been cancelled (`CanceledByPayer` or `CanceledByServiceProvider`). Integrators inspecting the returned state cannot distinguish cancelled agreements from live ones without reading separate storage. Document this behavior in the interface, or extend the state bitmask with a `CANCELED` flag and return it for the non-active terminal states. + +--- + +Reusing the existing interface flags instead of adding a `CANCELED` flag. `getAgreementDetails` now composes cancel and collectability information: + +- `NOTICE_GIVEN` — set on cancelled agreements (collection window truncated). +- `BY_PAYER` / `BY_PROVIDER` — paired with `NOTICE_GIVEN` to identify the cancel origin. +- `SETTLED` — set when nothing currently claimable. Covers provider-cancelled agreements (immediately non-collectable), fully-collected agreements, payer-cancelled agreements past their canceledAt window. + +`ACCEPTED` is also narrowed: it is now only set on the active-slot version (`VERSION_CURRENT`) of agreements past `NotAccepted`, so pending updates (`VERSION_NEXT`) no longer report `ACCEPTED`. Integrators distinguish cancelled-vs-live by `NOTICE_GIVEN`, and stop-collecting-now via `SETTLED`. See the TRST-R-11 fix for the accompanying flag cleanup. From 4a554f2966c632eb611eee1e93daacddf91d9667 Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Sun, 19 Apr 2026 20:27:32 +0000 Subject: [PATCH 30/31] feat(collector): add SCOPE_SIGNED to cancel() for EOA offer revocation (TRST-L-8) Give EOA signers an on-chain revocation path via cancel(agreementId, termsHash, SCOPE_SIGNED). Records cancelledOffers[msg.sender][termsHash] = agreementId; _requireAuthorization rejects when the stored agreementId matches. Self-authenticating, idempotent, reversible (bytes16(0) undoes), and combinable with SCOPE_PENDING/SCOPE_ACTIVE. Builds on the version-indexed storage and idempotent cancel semantics from the preceding L-11 refactor: SCOPE_SIGNED is added as a new branch at the top of cancel() alongside the existing SCOPE_PENDING / SCOPE_ACTIVE handling, and the cancelledOffers lookup slots into _requireAuthorization's signed branch. --- .../collectors/RecurringCollector.sol | 36 ++- .../recurring-collector/cancelSignature.t.sol | 256 ++++++++++++++++++ .../contracts/horizon/IAgreementCollector.sol | 14 +- .../contracts/horizon/IRecurringCollector.sol | 7 + packages/issuance/audits/PR1301/TRST-L-8.md | 2 + 5 files changed, 305 insertions(+), 10 deletions(-) create mode 100644 packages/horizon/test/unit/payments/recurring-collector/cancelSignature.t.sol diff --git a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol index 3af28e433..088e94e12 100644 --- a/packages/horizon/contracts/payments/collectors/RecurringCollector.sol +++ b/packages/horizon/contracts/payments/collectors/RecurringCollector.sol @@ -30,7 +30,8 @@ import { VERSION_CURRENT, VERSION_NEXT, SCOPE_ACTIVE, - SCOPE_PENDING + SCOPE_PENDING, + SCOPE_SIGNED } from "@graphprotocol/interfaces/contracts/horizon/IAgreementCollector.sol"; import { IRecurringCollector } from "@graphprotocol/interfaces/contracts/horizon/IRecurringCollector.sol"; import { IGraphPayments } from "@graphprotocol/interfaces/contracts/horizon/IGraphPayments.sol"; @@ -115,6 +116,9 @@ contract RecurringCollector is /// @notice Decoded agreement terms, keyed by EIP-712 hash. /// Referenced by AgreementData.activeTermsHash and pendingTermsHash. mapping(bytes32 termsHash => AgreementTerms terms) terms; + /// @notice Cancelled offer hashes, keyed by signer then EIP-712 hash. + /// Stores the agreementId that is blocked; bytes16(0) means not cancelled. + mapping(address signer => mapping(bytes32 hash => bytes16 agreementId)) cancelledOffers; } /// @dev keccak256(abi.encode(uint256(keccak256("graphprotocol.storage.RecurringCollector")) - 1)) & ~bytes32(uint256(0xff)) @@ -489,10 +493,28 @@ contract RecurringCollector is } /// @inheritdoc IAgreementCollector + /// @dev This implementation targets only the payer side of the agreement. + /// SCOPE_PENDING and SCOPE_ACTIVE enforce `msg.sender == agreement.payer`. + /// SCOPE_SIGNED has no caller check in this function; the entry it writes is + /// self-keyed by msg.sender and is consulted only later, during payer + /// authorization of a signed accept or update. Extending cancel to data-service + /// or service-provider callers is left for a future revision. function cancel(bytes16 agreementId, bytes32 termsHash, uint16 options) external whenNotPaused { RecurringCollectorStorage storage $ = _getStorage(); AgreementData storage agreement = $.agreements[agreementId]; + // Signed scope: record cancelledOffers[msg.sender][termsHash] = agreementId. + // Self-authenticating — only blocks when msg.sender matches the recovered ECDSA signer. + // The stored agreementId is checked in _requireAuthorization (!=); calling again + // with bytes16(0) undoes the cancellation, calling with a different agreementId + // redirects it. + if (options & SCOPE_SIGNED != 0) { + if ($.cancelledOffers[msg.sender][termsHash] != agreementId) { + $.cancelledOffers[msg.sender][termsHash] = agreementId; + emit OfferCancelled(msg.sender, agreementId, termsHash); + } + } + // Pending / active scopes: revert if on-chain data exists but caller is not the payer. // No-op if nothing exists on-chain (nothing to cancel). if (options & (SCOPE_PENDING | SCOPE_ACTIVE) != 0) { @@ -1063,11 +1085,15 @@ contract RecurringCollector is bytes16 _agreementId, uint8 _offerType ) private view { - if (0 < _signature.length) - require(_isAuthorized(_payer, ECDSA.recover(_hash, _signature)), RecurringCollectorInvalidSigner()); - else { + RecurringCollectorStorage storage $ = _getStorage(); + + if (0 < _signature.length) { + address signer = ECDSA.recover(_hash, _signature); + require(_isAuthorized(_payer, signer), RecurringCollectorInvalidSigner()); + require($.cancelledOffers[signer][_hash] != _agreementId, RecurringCollectorOfferCancelled(signer, _hash)); + } else { // Pre-approval: the hash must match the expected version of this agreement. - AgreementData storage agreement = _getStorage().agreements[_agreementId]; + AgreementData storage agreement = $.agreements[_agreementId]; bytes32 versionHash = _offerType == OFFER_TYPE_NEW ? agreement.activeTermsHash : agreement.pendingTermsHash; require(versionHash == _hash, RecurringCollectorInvalidSigner()); } diff --git a/packages/horizon/test/unit/payments/recurring-collector/cancelSignature.t.sol b/packages/horizon/test/unit/payments/recurring-collector/cancelSignature.t.sol new file mode 100644 index 000000000..9dadf2f6a --- /dev/null +++ b/packages/horizon/test/unit/payments/recurring-collector/cancelSignature.t.sol @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import { IRecurringCollector } from "@graphprotocol/interfaces/contracts/horizon/IRecurringCollector.sol"; +import { + SCOPE_SIGNED, + SCOPE_ACTIVE, + SCOPE_PENDING +} from "@graphprotocol/interfaces/contracts/horizon/IAgreementCollector.sol"; + +import { RecurringCollectorSharedTest } from "./shared.t.sol"; + +contract RecurringCollectorCancelSignedOfferTest is RecurringCollectorSharedTest { + /* + * TESTS + */ + + /* solhint-disable graph/func-name-mixedcase */ + + function test_CancelSigned_BlocksAccept(FuzzyTestAccept calldata fuzzyTestAccept) public { + IRecurringCollector.RecurringCollectionAgreement memory rca = _recurringCollectorHelper.sensibleRCA( + fuzzyTestAccept.rca + ); + uint256 signerKey = boundKey(fuzzyTestAccept.unboundedSignerKey); + _recurringCollectorHelper.authorizeSignerWithChecks(rca.payer, signerKey); + + (, bytes memory signature) = _recurringCollectorHelper.generateSignedRCA(rca, signerKey); + bytes32 rcaHash = _recurringCollector.hashRCA(rca); + address signer = vm.addr(signerKey); + bytes16 agreementId = _recurringCollector.generateAgreementId( + rca.payer, + rca.dataService, + rca.serviceProvider, + rca.deadline, + rca.nonce + ); + + vm.prank(signer); + _recurringCollector.cancel(agreementId, rcaHash, SCOPE_SIGNED); + + // Accepting with the cancelled signature should revert + vm.expectRevert( + abi.encodeWithSelector(IRecurringCollector.RecurringCollectorOfferCancelled.selector, signer, rcaHash) + ); + vm.prank(rca.dataService); + _recurringCollector.accept(rca, signature); + } + + function test_CancelSigned_EmitsEvent(FuzzyTestAccept calldata fuzzyTestAccept) public { + IRecurringCollector.RecurringCollectionAgreement memory rca = _recurringCollectorHelper.sensibleRCA( + fuzzyTestAccept.rca + ); + uint256 signerKey = boundKey(fuzzyTestAccept.unboundedSignerKey); + _recurringCollectorHelper.authorizeSignerWithChecks(rca.payer, signerKey); + + bytes32 rcaHash = _recurringCollector.hashRCA(rca); + address signer = vm.addr(signerKey); + bytes16 agreementId = _recurringCollector.generateAgreementId( + rca.payer, + rca.dataService, + rca.serviceProvider, + rca.deadline, + rca.nonce + ); + + vm.expectEmit(address(_recurringCollector)); + emit IRecurringCollector.OfferCancelled(signer, agreementId, rcaHash); + vm.prank(signer); + _recurringCollector.cancel(agreementId, rcaHash, SCOPE_SIGNED); + } + + function test_CancelSigned_BlocksUpdate(FuzzyTestUpdate calldata fuzzyTestUpdate) public { + ( + IRecurringCollector.RecurringCollectionAgreement memory rca, + , + uint256 signerKey, + bytes16 agreementId + ) = _sensibleAuthorizeAndAccept(fuzzyTestUpdate.fuzzyTestAccept); + + IRecurringCollector.RecurringCollectionAgreementUpdate memory rcau = _recurringCollectorHelper.sensibleRCAU( + fuzzyTestUpdate.rcau + ); + rcau.agreementId = agreementId; + + ( + IRecurringCollector.RecurringCollectionAgreementUpdate memory signedRcau, + bytes memory rcauSig + ) = _recurringCollectorHelper.generateSignedRCAUForAgreement(agreementId, rcau, signerKey); + bytes32 rcauHash = _recurringCollector.hashRCAU(signedRcau); + address signer = vm.addr(signerKey); + + vm.prank(signer); + _recurringCollector.cancel(agreementId, rcauHash, SCOPE_SIGNED); + + // Updating with the cancelled signature should revert + vm.expectRevert( + abi.encodeWithSelector(IRecurringCollector.RecurringCollectorOfferCancelled.selector, signer, rcauHash) + ); + vm.prank(rca.dataService); + _recurringCollector.update(signedRcau, rcauSig); + } + + function test_CancelSigned_Idempotent(FuzzyTestAccept calldata fuzzyTestAccept) public { + IRecurringCollector.RecurringCollectionAgreement memory rca = _recurringCollectorHelper.sensibleRCA( + fuzzyTestAccept.rca + ); + uint256 signerKey = boundKey(fuzzyTestAccept.unboundedSignerKey); + _recurringCollectorHelper.authorizeSignerWithChecks(rca.payer, signerKey); + + bytes32 rcaHash = _recurringCollector.hashRCA(rca); + address signer = vm.addr(signerKey); + bytes16 agreementId = _recurringCollector.generateAgreementId( + rca.payer, + rca.dataService, + rca.serviceProvider, + rca.deadline, + rca.nonce + ); + + vm.prank(signer); + _recurringCollector.cancel(agreementId, rcaHash, SCOPE_SIGNED); + + // Second call succeeds silently — no revert, no event + vm.recordLogs(); + vm.prank(signer); + _recurringCollector.cancel(agreementId, rcaHash, SCOPE_SIGNED); + assertEq(vm.getRecordedLogs().length, 0); + } + + function test_CancelSigned_DoesNotAffectDifferentSigner( + FuzzyTestAccept calldata fuzzyTestAccept1, + FuzzyTestAccept calldata fuzzyTestAccept2 + ) public { + IRecurringCollector.RecurringCollectionAgreement memory rca1 = _recurringCollectorHelper.sensibleRCA( + fuzzyTestAccept1.rca + ); + uint256 signerKey1 = boundKey(fuzzyTestAccept1.unboundedSignerKey); + + IRecurringCollector.RecurringCollectionAgreement memory rca2 = _recurringCollectorHelper.sensibleRCA( + fuzzyTestAccept2.rca + ); + uint256 signerKey2 = boundKey(fuzzyTestAccept2.unboundedSignerKey); + + vm.assume(rca1.payer != rca2.payer); + vm.assume(vm.addr(signerKey1) != vm.addr(signerKey2)); + + _recurringCollectorHelper.authorizeSignerWithChecks(rca1.payer, signerKey1); + _recurringCollectorHelper.authorizeSignerWithChecks(rca2.payer, signerKey2); + + bytes32 rcaHash = _recurringCollector.hashRCA(rca1); + + // Signer1 cancels — should not affect signer2 + vm.prank(vm.addr(signerKey1)); + _recurringCollector.cancel(bytes16(0), rcaHash, SCOPE_SIGNED); + + // Signer2's signatures for the same hash are unaffected + // (signer-scoped, not hash-global) + } + + function test_CancelSigned_SelfAuthenticating(FuzzyTestAccept calldata fuzzyTestAccept, address anyAddress) public { + // Any address can call cancel with SCOPE_SIGNED — it only records for msg.sender + IRecurringCollector.RecurringCollectionAgreement memory rca = _recurringCollectorHelper.sensibleRCA( + fuzzyTestAccept.rca + ); + bytes32 rcaHash = _recurringCollector.hashRCA(rca); + vm.assume(anyAddress != address(0)); + vm.assume(anyAddress != _proxyAdmin); + + // Should not revert — self-authenticating, no _requirePayer + vm.prank(anyAddress); + _recurringCollector.cancel(bytes16(0), rcaHash, SCOPE_SIGNED); + } + + function test_CancelSigned_CombinedWithActiveDoesNotRevert(FuzzyTestAccept calldata fuzzyTestAccept) public { + IRecurringCollector.RecurringCollectionAgreement memory rca = _recurringCollectorHelper.sensibleRCA( + fuzzyTestAccept.rca + ); + uint256 signerKey = boundKey(fuzzyTestAccept.unboundedSignerKey); + _recurringCollectorHelper.authorizeSignerWithChecks(rca.payer, signerKey); + + bytes32 rcaHash = _recurringCollector.hashRCA(rca); + address signer = vm.addr(signerKey); + bytes16 agreementId = _recurringCollector.generateAgreementId( + rca.payer, + rca.dataService, + rca.serviceProvider, + rca.deadline, + rca.nonce + ); + + // SCOPE_SIGNED | SCOPE_ACTIVE with no accepted agreement — should not revert. + // The signed recording succeeds; the active scope is skipped because nothing on-chain. + vm.expectEmit(address(_recurringCollector)); + emit IRecurringCollector.OfferCancelled(signer, agreementId, rcaHash); + vm.prank(signer); + _recurringCollector.cancel(agreementId, rcaHash, SCOPE_SIGNED | SCOPE_ACTIVE); + } + + function test_CancelSigned_CombinedWithPendingDoesNotRevert(FuzzyTestAccept calldata fuzzyTestAccept) public { + IRecurringCollector.RecurringCollectionAgreement memory rca = _recurringCollectorHelper.sensibleRCA( + fuzzyTestAccept.rca + ); + uint256 signerKey = boundKey(fuzzyTestAccept.unboundedSignerKey); + _recurringCollectorHelper.authorizeSignerWithChecks(rca.payer, signerKey); + + bytes32 rcaHash = _recurringCollector.hashRCA(rca); + address signer = vm.addr(signerKey); + bytes16 agreementId = _recurringCollector.generateAgreementId( + rca.payer, + rca.dataService, + rca.serviceProvider, + rca.deadline, + rca.nonce + ); + + // SCOPE_SIGNED | SCOPE_PENDING with no agreement — should not revert. + vm.expectEmit(address(_recurringCollector)); + emit IRecurringCollector.OfferCancelled(signer, agreementId, rcaHash); + vm.prank(signer); + _recurringCollector.cancel(agreementId, rcaHash, SCOPE_SIGNED | SCOPE_PENDING); + } + + function test_CancelSigned_UndoWithZero(FuzzyTestAccept calldata fuzzyTestAccept) public { + IRecurringCollector.RecurringCollectionAgreement memory rca = _recurringCollectorHelper.sensibleRCA( + fuzzyTestAccept.rca + ); + uint256 signerKey = boundKey(fuzzyTestAccept.unboundedSignerKey); + _recurringCollectorHelper.authorizeSignerWithChecks(rca.payer, signerKey); + + (, bytes memory signature) = _recurringCollectorHelper.generateSignedRCA(rca, signerKey); + bytes32 rcaHash = _recurringCollector.hashRCA(rca); + address signer = vm.addr(signerKey); + bytes16 agreementId = _recurringCollector.generateAgreementId( + rca.payer, + rca.dataService, + rca.serviceProvider, + rca.deadline, + rca.nonce + ); + + // Cancel + vm.prank(signer); + _recurringCollector.cancel(agreementId, rcaHash, SCOPE_SIGNED); + + // Undo by calling with bytes16(0) + vm.prank(signer); + _recurringCollector.cancel(bytes16(0), rcaHash, SCOPE_SIGNED); + + // Accept should now succeed + _setupValidProvision(rca.serviceProvider, rca.dataService); + vm.prank(rca.dataService); + _recurringCollector.accept(rca, signature); + } + + /* solhint-enable graph/func-name-mixedcase */ +} diff --git a/packages/interfaces/contracts/horizon/IAgreementCollector.sol b/packages/interfaces/contracts/horizon/IAgreementCollector.sol index 1654e2ff3..11a3f9a78 100644 --- a/packages/interfaces/contracts/horizon/IAgreementCollector.sol +++ b/packages/interfaces/contracts/horizon/IAgreementCollector.sol @@ -46,6 +46,8 @@ uint8 constant OFFER_TYPE_UPDATE = 2; uint8 constant SCOPE_ACTIVE = 1; /// @dev Cancel targets pending offers uint8 constant SCOPE_PENDING = 2; +/// @dev Cancel targets signed offers +uint8 constant SCOPE_SIGNED = 4; // -- Version indices (shared by getAgreementDetails and getAgreementOfferAt) -- // @@ -131,12 +133,14 @@ interface IAgreementCollector is IPaymentsCollector { function offer(uint8 offerType, bytes calldata data, uint16 options) external returns (AgreementDetails memory); /** - * @notice Cancel an agreement or revoke a pending offer. - * @dev Scopes can be combined. SCOPE_PENDING and SCOPE_ACTIVE require payer authorization - * and no-op if nothing exists on-chain. - * @param agreementId The agreement's ID. + * @notice Cancel an agreement, revoke a pending offer, or invalidate a signed offer. + * @dev Scopes can be combined. SCOPE_SIGNED is self-authenticating (keyed by msg.sender); + * SCOPE_PENDING and SCOPE_ACTIVE require payer authorization and no-op if nothing exists on-chain. + * @param agreementId The agreement's ID. For SCOPE_SIGNED, only blocks accept/update when + * the agreementId matches; passing bytes16(0) undoes a previous cancellation. * @param termsHash EIP-712 hash identifying which terms to cancel. - * @param options Bitmask — SCOPE_ACTIVE (1) active terms, SCOPE_PENDING (2) pending offers. + * @param options Bitmask — SCOPE_ACTIVE (1) active terms, SCOPE_PENDING (2) pending offers, + * SCOPE_SIGNED (4) signed offers. */ function cancel(bytes16 agreementId, bytes32 termsHash, uint16 options) external; diff --git a/packages/interfaces/contracts/horizon/IRecurringCollector.sol b/packages/interfaces/contracts/horizon/IRecurringCollector.sol index 052c7850d..1937d4a95 100644 --- a/packages/interfaces/contracts/horizon/IRecurringCollector.sol +++ b/packages/interfaces/contracts/horizon/IRecurringCollector.sol @@ -402,6 +402,13 @@ interface IRecurringCollector is IAuthorizable, IAgreementCollector { */ error RecurringCollectorPauseGuardianNoChange(address account, bool allowed); + /** + * @notice Thrown when accepting or updating with a hash that the signer cancelled via SCOPE_SIGNED + * @param signer The signer who cancelled the offer + * @param hash The cancelled EIP-712 hash + */ + error RecurringCollectorOfferCancelled(address signer, bytes32 hash); + /** * @notice Emitted when a pause guardian is set * @param account The address of the pause guardian diff --git a/packages/issuance/audits/PR1301/TRST-L-8.md b/packages/issuance/audits/PR1301/TRST-L-8.md index 90911d2d3..c85f413d0 100644 --- a/packages/issuance/audits/PR1301/TRST-L-8.md +++ b/packages/issuance/audits/PR1301/TRST-L-8.md @@ -20,3 +20,5 @@ Expose a `cancelSignature(bytes32 hash)` entry point that records the hash as in TBD --- + +Added `SCOPE_SIGNED` flag to `cancel()`, giving EOA signers an on-chain revocation path like contract payers already have via `SCOPE_PENDING`. The signer calls `cancel(agreementId, termsHash, SCOPE_SIGNED)` which records `cancelledOffers[msg.sender][termsHash] = agreementId`. When `accept()` or `update()` later processes a signature, `_requireAuthorization` recovers the signer via ECDSA and rejects if the stored agreementId matches. Self-authenticating (keyed by signer address), idempotent, reversible (calling again with `bytes16(0)` undoes the cancellation), and combinable with other scopes. Also made `cancel` no-op when nothing exists on-chain instead of reverting. From a9c2737038115be4ccdf565ad434cb561e22353f Mon Sep 17 00:00:00 2001 From: Rembrandt Kuipers <50174308+RembrandtK@users.noreply.github.com> Date: Fri, 10 Apr 2026 09:24:57 +0000 Subject: [PATCH 31/31] feat(contracts): add getIssuanceAllocator to IIssuanceTarget interface Every issuance target should expose its allocator. Add getIssuanceAllocator() returning IIssuanceAllocationDistribution to IIssuanceTarget. Implement in RecurringAgreementManager (reads from storage), DirectAllocation (stores and returns), and RewardsManager (existing impl, moved from IRewardsManager to IIssuanceTarget). Also change IIssuanceTarget.setIssuanceAllocator parameter from address to IIssuanceAllocationDistribution for compile-time type safety. --- .../unit/rewards/rewards-interface.test.ts | 4 +- .../contracts/rewards/RewardsManager.sol | 17 ++-- packages/deployment/lib/abis.ts | 10 +- .../test/interface-id-stability.test.ts | 34 +++++++ .../contracts/rewards/IRewardsManager.sol | 8 -- .../issuance/allocate/IIssuanceTarget.sol | 15 ++- .../agreement/RecurringAgreementManager.sol | 21 +++-- .../contracts/allocate/DirectAllocation.sol | 66 ++++++++++++- .../test/allocate/MockNotificationTracker.sol | 8 +- .../test/allocate/MockReentrantTarget.sol | 9 +- .../test/allocate/MockRevertingTarget.sol | 10 +- .../test/allocate/MockSimpleTarget.sol | 8 +- .../unit/agreement-manager/approver.t.sol | 5 +- .../agreement-manager/branchCoverage.t.sol | 77 ++++++++++++++- .../unit/agreement-manager/callbackGas.t.sol | 3 +- .../agreement-manager/ensureDistributed.t.sol | 21 +++-- .../test/unit/allocator/distribution.t.sol | 3 +- .../unit/allocator/interfaceIdStability.t.sol | 2 +- .../direct-allocation/DirectAllocation.t.sol | 93 ++++++++++++++++++- .../test/harness/FullStackHarness.t.sol | 3 +- .../test/harness/RealStackHarness.t.sol | 3 +- 21 files changed, 356 insertions(+), 64 deletions(-) create mode 100644 packages/deployment/test/interface-id-stability.test.ts diff --git a/packages/contracts-test/tests/unit/rewards/rewards-interface.test.ts b/packages/contracts-test/tests/unit/rewards/rewards-interface.test.ts index 63280f5e8..7bbfebe6b 100644 --- a/packages/contracts-test/tests/unit/rewards/rewards-interface.test.ts +++ b/packages/contracts-test/tests/unit/rewards/rewards-interface.test.ts @@ -54,11 +54,11 @@ describe('RewardsManager interfaces', () => { }) it('IIssuanceTarget should have stable interface ID', () => { - expect(IIssuanceTarget__factory.interfaceId).to.equal('0xaee4dc43') + expect(IIssuanceTarget__factory.interfaceId).to.equal('0x19f6601a') }) it('IRewardsManager should have stable interface ID', () => { - expect(IRewardsManager__factory.interfaceId).to.equal('0x337b092e') + expect(IRewardsManager__factory.interfaceId).to.equal('0x8469b577') }) }) diff --git a/packages/contracts/contracts/rewards/RewardsManager.sol b/packages/contracts/contracts/rewards/RewardsManager.sol index a0ca5ca20..f251dc5f8 100644 --- a/packages/contracts/contracts/rewards/RewardsManager.sol +++ b/packages/contracts/contracts/rewards/RewardsManager.sol @@ -173,24 +173,25 @@ contract RewardsManager is * Note that the IssuanceAllocator can be set to the zero address to disable use of an allocator, and * use the local `issuancePerBlock` variable instead to control issuance. */ - function setIssuanceAllocator(address newIssuanceAllocator) external override onlyGovernor { - if (address(issuanceAllocator) != newIssuanceAllocator) { + function setIssuanceAllocator(IIssuanceAllocationDistribution newIssuanceAllocator) external override onlyGovernor { + if (issuanceAllocator != newIssuanceAllocator) { // Update rewards calculation before changing the issuance allocator updateAccRewardsPerSignal(); // Check that the contract supports the IIssuanceAllocationDistribution interface // Allow zero address to disable the allocator - if (newIssuanceAllocator != address(0)) { + if (address(newIssuanceAllocator) != address(0)) { // solhint-disable-next-line gas-small-strings require( - IERC165(newIssuanceAllocator).supportsInterface(type(IIssuanceAllocationDistribution).interfaceId), + IERC165(address(newIssuanceAllocator)).supportsInterface( + type(IIssuanceAllocationDistribution).interfaceId + ), "Contract does not support IIssuanceAllocationDistribution interface" ); } - address oldIssuanceAllocator = address(issuanceAllocator); - issuanceAllocator = IIssuanceAllocationDistribution(newIssuanceAllocator); - emit IssuanceAllocatorSet(oldIssuanceAllocator, newIssuanceAllocator); + emit IssuanceAllocatorSet(issuanceAllocator, newIssuanceAllocator); + issuanceAllocator = newIssuanceAllocator; } } @@ -325,7 +326,7 @@ contract RewardsManager is } /** - * @inheritdoc IRewardsManager + * @inheritdoc IIssuanceTarget */ function getIssuanceAllocator() external view override returns (IIssuanceAllocationDistribution) { return issuanceAllocator; diff --git a/packages/deployment/lib/abis.ts b/packages/deployment/lib/abis.ts index e9894d213..b7b0868b2 100644 --- a/packages/deployment/lib/abis.ts +++ b/packages/deployment/lib/abis.ts @@ -17,12 +17,12 @@ function loadAbi(artifactPath: string): Abi { return artifact.abi as Abi } -// Interface IDs - these match the generated values from TypeChain factories -// Verified by tests: packages/issuance/testing/tests/allocate/InterfaceIdStability.test.ts -// and packages/contracts-test/tests/unit/rewards/rewards-interface.test.ts +// Interface IDs - these mirror the values the compiler derives from the +// corresponding ABI. Cross-checked by test/interface-id-stability.test.ts; +// update both together whenever an interface changes. export const IERC165_INTERFACE_ID = '0x01ffc9a7' as const -export const IISSUANCE_TARGET_INTERFACE_ID = '0xaee4dc43' as const -export const IREWARDS_MANAGER_INTERFACE_ID = '0xa0a2f219' as const +export const IISSUANCE_TARGET_INTERFACE_ID = '0x19f6601a' as const +export const IREWARDS_MANAGER_INTERFACE_ID = '0x8469b577' as const export const REWARDS_MANAGER_ABI = loadAbi( '@graphprotocol/interfaces/artifacts/contracts/contracts/rewards/IRewardsManager.sol/IRewardsManager.json', diff --git a/packages/deployment/test/interface-id-stability.test.ts b/packages/deployment/test/interface-id-stability.test.ts new file mode 100644 index 000000000..5d0ed1225 --- /dev/null +++ b/packages/deployment/test/interface-id-stability.test.ts @@ -0,0 +1,34 @@ +import { expect } from 'chai' +import type { Abi } from 'viem' +import { toFunctionSelector } from 'viem' + +import { + IERC165_ABI, + IERC165_INTERFACE_ID, + IISSUANCE_TARGET_INTERFACE_ID, + IREWARDS_MANAGER_INTERFACE_ID, + ISSUANCE_TARGET_ABI, + REWARDS_MANAGER_ABI, +} from '../lib/abis.js' + +function computeInterfaceId(abi: Abi): `0x${string}` { + const xor = abi + .filter((item): item is Extract<(typeof abi)[number], { type: 'function' }> => item.type === 'function') + .map((f) => Number.parseInt(toFunctionSelector(f).slice(2), 16) >>> 0) + .reduce((a, s) => (a ^ s) >>> 0, 0) + return `0x${xor.toString(16).padStart(8, '0')}` +} + +describe('Interface ID Stability', function () { + it('IERC165_INTERFACE_ID matches the IERC165 ABI', function () { + expect(IERC165_INTERFACE_ID).to.equal(computeInterfaceId(IERC165_ABI)) + }) + + it('IISSUANCE_TARGET_INTERFACE_ID matches the IIssuanceTarget ABI', function () { + expect(IISSUANCE_TARGET_INTERFACE_ID).to.equal(computeInterfaceId(ISSUANCE_TARGET_ABI)) + }) + + it('IREWARDS_MANAGER_INTERFACE_ID matches the IRewardsManager ABI', function () { + expect(IREWARDS_MANAGER_INTERFACE_ID).to.equal(computeInterfaceId(REWARDS_MANAGER_ABI)) + }) +}) diff --git a/packages/interfaces/contracts/contracts/rewards/IRewardsManager.sol b/packages/interfaces/contracts/contracts/rewards/IRewardsManager.sol index 205bde73c..688c9469d 100644 --- a/packages/interfaces/contracts/contracts/rewards/IRewardsManager.sol +++ b/packages/interfaces/contracts/contracts/rewards/IRewardsManager.sol @@ -2,7 +2,6 @@ pragma solidity ^0.7.6 || ^0.8.0; -import { IIssuanceAllocationDistribution } from "../../issuance/allocate/IIssuanceAllocationDistribution.sol"; import { IRewardsIssuer } from "./IRewardsIssuer.sol"; /** @@ -179,13 +178,6 @@ interface IRewardsManager { */ function subgraphService() external view returns (IRewardsIssuer); - /** - * @notice Get the issuance allocator address - * @dev When set, this allocator controls issuance distribution instead of issuancePerBlock - * @return The issuance allocator contract (zero address if not set) - */ - function getIssuanceAllocator() external view returns (IIssuanceAllocationDistribution); - /** * @notice Get the reclaim address for a specific reason * @param reason The reclaim reason identifier diff --git a/packages/interfaces/contracts/issuance/allocate/IIssuanceTarget.sol b/packages/interfaces/contracts/issuance/allocate/IIssuanceTarget.sol index 90a311556..ed9f60b8f 100644 --- a/packages/interfaces/contracts/issuance/allocate/IIssuanceTarget.sol +++ b/packages/interfaces/contracts/issuance/allocate/IIssuanceTarget.sol @@ -2,6 +2,8 @@ pragma solidity ^0.7.6 || ^0.8.0; +import { IIssuanceAllocationDistribution } from "./IIssuanceAllocationDistribution.sol"; + /** * @title IIssuanceTarget * @author Edge & Node @@ -13,7 +15,10 @@ interface IIssuanceTarget { * @param oldIssuanceAllocator Old issuance allocator address * @param newIssuanceAllocator New issuance allocator address */ - event IssuanceAllocatorSet(address indexed oldIssuanceAllocator, address indexed newIssuanceAllocator); + event IssuanceAllocatorSet( + IIssuanceAllocationDistribution indexed oldIssuanceAllocator, + IIssuanceAllocationDistribution indexed newIssuanceAllocator + ); /// @notice Emitted before the issuance allocation changes event BeforeIssuanceAllocationChange(); @@ -27,11 +32,17 @@ interface IIssuanceTarget { */ function beforeIssuanceAllocationChange() external; + /** + * @notice Returns the current issuance allocator + * @return The issuance allocator contract (zero address if not set) + */ + function getIssuanceAllocator() external view returns (IIssuanceAllocationDistribution); + /** * @notice Sets the issuance allocator for this target * @dev This function facilitates upgrades by providing a standard way for targets * to change their allocator. Implementations can define their own access control. * @param newIssuanceAllocator Address of the issuance allocator */ - function setIssuanceAllocator(address newIssuanceAllocator) external; + function setIssuanceAllocator(IIssuanceAllocationDistribution newIssuanceAllocator) external; } diff --git a/packages/issuance/contracts/agreement/RecurringAgreementManager.sol b/packages/issuance/contracts/agreement/RecurringAgreementManager.sol index a5f3c40b0..4993ba3fe 100644 --- a/packages/issuance/contracts/agreement/RecurringAgreementManager.sol +++ b/packages/issuance/contracts/agreement/RecurringAgreementManager.sol @@ -275,6 +275,11 @@ contract RecurringAgreementManager is /// @inheritdoc IIssuanceTarget function beforeIssuanceAllocationChange() external virtual override {} + /// @inheritdoc IIssuanceTarget + function getIssuanceAllocator() external view virtual override returns (IIssuanceAllocationDistribution) { + return _getStorage().issuanceAllocator; + } + /// @inheritdoc IIssuanceTarget /// @dev The allocator is expected to call distributeIssuance() (bringing distribution up to /// the current block) before any configuration change. As a result, the same-block dedup in @@ -283,21 +288,23 @@ contract RecurringAgreementManager is /// in a standalone transaction to avoid interleaving with collection in the same block. /// Even if interleaved, the only effect is a one-block lag before the new allocator's /// distribution is picked up — corrected automatically on the next block. - function setIssuanceAllocator(address newIssuanceAllocator) external virtual override onlyRole(GOVERNOR_ROLE) { + function setIssuanceAllocator( + IIssuanceAllocationDistribution newIssuanceAllocator + ) external virtual override onlyRole(GOVERNOR_ROLE) { RecurringAgreementManagerStorage storage $ = _getStorage(); - if (address($.issuanceAllocator) == newIssuanceAllocator) return; + if (address($.issuanceAllocator) == address(newIssuanceAllocator)) return; - if (newIssuanceAllocator != address(0)) + if (address(newIssuanceAllocator) != address(0)) require( ERC165Checker.supportsInterface( - newIssuanceAllocator, + address(newIssuanceAllocator), type(IIssuanceAllocationDistribution).interfaceId ), - InvalidIssuanceAllocator(newIssuanceAllocator) + InvalidIssuanceAllocator(address(newIssuanceAllocator)) ); - emit IssuanceAllocatorSet(address($.issuanceAllocator), newIssuanceAllocator); - $.issuanceAllocator = IIssuanceAllocationDistribution(newIssuanceAllocator); + emit IssuanceAllocatorSet($.issuanceAllocator, newIssuanceAllocator); + $.issuanceAllocator = newIssuanceAllocator; } // -- IAgreementOwner -- diff --git a/packages/issuance/contracts/allocate/DirectAllocation.sol b/packages/issuance/contracts/allocate/DirectAllocation.sol index 91f153b5e..9df058eca 100644 --- a/packages/issuance/contracts/allocate/DirectAllocation.sol +++ b/packages/issuance/contracts/allocate/DirectAllocation.sol @@ -2,6 +2,9 @@ pragma solidity ^0.8.27; +import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; + +import { IIssuanceAllocationDistribution } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol"; import { IIssuanceTarget } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceTarget.sol"; import { ISendTokens } from "@graphprotocol/interfaces/contracts/issuance/allocate/ISendTokens.sol"; import { BaseUpgradeable } from "../common/BaseUpgradeable.sol"; @@ -24,6 +27,36 @@ import { ERC165Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/int * @custom:security-contact Please email security+contracts@thegraph.com if you find any bugs. We might have an active bug bounty program. */ contract DirectAllocation is BaseUpgradeable, IIssuanceTarget, ISendTokens { + // -- Namespaced Storage -- + + /// @notice ERC-7201 storage location for DirectAllocation + bytes32 private constant DIRECT_ALLOCATION_STORAGE_LOCATION = + // solhint-disable-next-line gas-small-strings + keccak256(abi.encode(uint256(keccak256("graphprotocol.storage.DirectAllocation")) - 1)) & + ~bytes32(uint256(0xff)); + + /// @notice Main storage structure for DirectAllocation using ERC-7201 namespaced storage + /// @param issuanceAllocator The issuance allocator that distributes tokens to this contract + /// @custom:storage-location erc7201:graphprotocol.storage.DirectAllocation + struct DirectAllocationData { + IIssuanceAllocationDistribution issuanceAllocator; + } + + /** + * @notice Returns the storage struct for DirectAllocation + * @return $ contract storage + */ + function _getDirectAllocationStorage() private pure returns (DirectAllocationData storage $) { + // solhint-disable-previous-line use-natspec + // Solhint does not support $ return variable in natspec + + bytes32 slot = DIRECT_ALLOCATION_STORAGE_LOCATION; + // solhint-disable-next-line no-inline-assembly + assembly { + $.slot := slot + } + } + // -- Custom Errors -- /// @notice Thrown when token transfer fails @@ -31,6 +64,10 @@ contract DirectAllocation is BaseUpgradeable, IIssuanceTarget, ISendTokens { /// @param amount The amount of tokens that failed to transfer error SendTokensFailed(address to, uint256 amount); + /// @notice Thrown when the issuance allocator does not support IIssuanceAllocationDistribution + /// @param allocator The rejected allocator address + error InvalidIssuanceAllocator(address allocator); + // -- Events -- /// @notice Emitted when tokens are sent @@ -89,9 +126,28 @@ contract DirectAllocation is BaseUpgradeable, IIssuanceTarget, ISendTokens { */ function beforeIssuanceAllocationChange() external virtual override {} - /** - * @dev No-op for DirectAllocation; issuanceAllocator is not stored. - * @inheritdoc IIssuanceTarget - */ - function setIssuanceAllocator(address issuanceAllocator) external virtual override onlyRole(GOVERNOR_ROLE) {} + /// @inheritdoc IIssuanceTarget + function getIssuanceAllocator() external view virtual override returns (IIssuanceAllocationDistribution) { + return _getDirectAllocationStorage().issuanceAllocator; + } + + /// @inheritdoc IIssuanceTarget + function setIssuanceAllocator( + IIssuanceAllocationDistribution newIssuanceAllocator + ) external virtual override onlyRole(GOVERNOR_ROLE) { + DirectAllocationData storage $ = _getDirectAllocationStorage(); + if (address(newIssuanceAllocator) == address($.issuanceAllocator)) return; + + if (address(newIssuanceAllocator) != address(0)) + require( + ERC165Checker.supportsInterface( + address(newIssuanceAllocator), + type(IIssuanceAllocationDistribution).interfaceId + ), + InvalidIssuanceAllocator(address(newIssuanceAllocator)) + ); + + emit IssuanceAllocatorSet($.issuanceAllocator, newIssuanceAllocator); + $.issuanceAllocator = newIssuanceAllocator; + } } diff --git a/packages/issuance/contracts/test/allocate/MockNotificationTracker.sol b/packages/issuance/contracts/test/allocate/MockNotificationTracker.sol index a33212282..2b5fb5aec 100644 --- a/packages/issuance/contracts/test/allocate/MockNotificationTracker.sol +++ b/packages/issuance/contracts/test/allocate/MockNotificationTracker.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; +import { IIssuanceAllocationDistribution } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol"; import { IIssuanceTarget } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceTarget.sol"; import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; @@ -30,7 +31,12 @@ contract MockNotificationTracker is IIssuanceTarget, ERC165 { } /// @inheritdoc IIssuanceTarget - function setIssuanceAllocator(address _issuanceAllocator) external pure override {} + function getIssuanceAllocator() external pure override returns (IIssuanceAllocationDistribution) { + return IIssuanceAllocationDistribution(address(0)); + } + + /// @inheritdoc IIssuanceTarget + function setIssuanceAllocator(IIssuanceAllocationDistribution _issuanceAllocator) external pure override {} /// @inheritdoc ERC165 function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { diff --git a/packages/issuance/contracts/test/allocate/MockReentrantTarget.sol b/packages/issuance/contracts/test/allocate/MockReentrantTarget.sol index 484648805..ffa4e5aae 100644 --- a/packages/issuance/contracts/test/allocate/MockReentrantTarget.sol +++ b/packages/issuance/contracts/test/allocate/MockReentrantTarget.sol @@ -85,8 +85,13 @@ contract MockReentrantTarget is IIssuanceTarget, ERC165 { } /// @inheritdoc IIssuanceTarget - function setIssuanceAllocator(address _issuanceAllocator) external override { - issuanceAllocator = _issuanceAllocator; + function getIssuanceAllocator() external view override returns (IIssuanceAllocationDistribution) { + return IIssuanceAllocationDistribution(issuanceAllocator); + } + + /// @inheritdoc IIssuanceTarget + function setIssuanceAllocator(IIssuanceAllocationDistribution _issuanceAllocator) external override { + issuanceAllocator = address(_issuanceAllocator); } /// @inheritdoc ERC165 diff --git a/packages/issuance/contracts/test/allocate/MockRevertingTarget.sol b/packages/issuance/contracts/test/allocate/MockRevertingTarget.sol index 27522e5a4..eb0ec1734 100644 --- a/packages/issuance/contracts/test/allocate/MockRevertingTarget.sol +++ b/packages/issuance/contracts/test/allocate/MockRevertingTarget.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; +import { IIssuanceAllocationDistribution } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol"; import { IIssuanceTarget } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceTarget.sol"; import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; @@ -23,7 +24,14 @@ contract MockRevertingTarget is IIssuanceTarget, ERC165 { /** * @inheritdoc IIssuanceTarget */ - function setIssuanceAllocator(address _issuanceAllocator) external pure override { + function getIssuanceAllocator() external pure override returns (IIssuanceAllocationDistribution) { + return IIssuanceAllocationDistribution(address(0)); + } + + /** + * @inheritdoc IIssuanceTarget + */ + function setIssuanceAllocator(IIssuanceAllocationDistribution _issuanceAllocator) external pure override { // No-op } diff --git a/packages/issuance/contracts/test/allocate/MockSimpleTarget.sol b/packages/issuance/contracts/test/allocate/MockSimpleTarget.sol index 311e1f03c..fddaed78b 100644 --- a/packages/issuance/contracts/test/allocate/MockSimpleTarget.sol +++ b/packages/issuance/contracts/test/allocate/MockSimpleTarget.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; +import { IIssuanceAllocationDistribution } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol"; import { IIssuanceTarget } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceTarget.sol"; import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; @@ -15,7 +16,12 @@ contract MockSimpleTarget is IIssuanceTarget, ERC165 { function beforeIssuanceAllocationChange() external pure override {} /// @inheritdoc IIssuanceTarget - function setIssuanceAllocator(address _issuanceAllocator) external pure override {} + function getIssuanceAllocator() external pure override returns (IIssuanceAllocationDistribution) { + return IIssuanceAllocationDistribution(address(0)); + } + + /// @inheritdoc IIssuanceTarget + function setIssuanceAllocator(IIssuanceAllocationDistribution _issuanceAllocator) external pure override {} /// @inheritdoc ERC165 function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { diff --git a/packages/issuance/test/unit/agreement-manager/approver.t.sol b/packages/issuance/test/unit/agreement-manager/approver.t.sol index f38db6a7c..488b74729 100644 --- a/packages/issuance/test/unit/agreement-manager/approver.t.sol +++ b/packages/issuance/test/unit/agreement-manager/approver.t.sol @@ -8,6 +8,7 @@ import { IRecurringEscrowManagement } from "@graphprotocol/interfaces/contracts/ import { IProviderEligibilityManagement } from "@graphprotocol/interfaces/contracts/issuance/eligibility/IProviderEligibilityManagement.sol"; import { IRecurringAgreements } from "@graphprotocol/interfaces/contracts/issuance/agreement/IRecurringAgreements.sol"; import { IIssuanceTarget } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceTarget.sol"; +import { IIssuanceAllocationDistribution } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol"; import { IAgreementCollector, OFFER_TYPE_NEW @@ -57,13 +58,13 @@ contract RecurringAgreementManagerApproverTest is RecurringAgreementManagerShare MockIssuanceAllocator alloc = new MockIssuanceAllocator(token, address(agreementManager)); vm.expectRevert(); vm.prank(nonGovernor); - agreementManager.setIssuanceAllocator(address(alloc)); + agreementManager.setIssuanceAllocator(IIssuanceAllocationDistribution(address(alloc))); } function test_SetIssuanceAllocator_Governor() public { MockIssuanceAllocator alloc = new MockIssuanceAllocator(token, address(agreementManager)); vm.prank(governor); - agreementManager.setIssuanceAllocator(address(alloc)); + agreementManager.setIssuanceAllocator(IIssuanceAllocationDistribution(address(alloc))); } // -- View Function Tests -- diff --git a/packages/issuance/test/unit/agreement-manager/branchCoverage.t.sol b/packages/issuance/test/unit/agreement-manager/branchCoverage.t.sol index 2b7db27a4..458e76347 100644 --- a/packages/issuance/test/unit/agreement-manager/branchCoverage.t.sol +++ b/packages/issuance/test/unit/agreement-manager/branchCoverage.t.sol @@ -7,6 +7,7 @@ import { IAgreementCollector } from "@graphprotocol/interfaces/contracts/horizon import { IRecurringCollector } from "@graphprotocol/interfaces/contracts/horizon/IRecurringCollector.sol"; import { IAgreementCollector } from "@graphprotocol/interfaces/contracts/horizon/IAgreementCollector.sol"; import { IRecurringAgreementManagement } from "@graphprotocol/interfaces/contracts/issuance/agreement/IRecurringAgreementManagement.sol"; +import { IIssuanceAllocationDistribution } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol"; import { IAgreementCollector } from "@graphprotocol/interfaces/contracts/horizon/IAgreementCollector.sol"; import { IAgreementCollector } from "@graphprotocol/interfaces/contracts/horizon/IAgreementCollector.sol"; @@ -15,6 +16,7 @@ import { IAgreementCollector } from "@graphprotocol/interfaces/contracts/horizon import { RecurringAgreementManagerSharedTest } from "./shared.t.sol"; import { IAgreementCollector } from "@graphprotocol/interfaces/contracts/horizon/IAgreementCollector.sol"; import { MockRecurringCollector } from "./mocks/MockRecurringCollector.sol"; +import { MockIssuanceAllocator } from "./mocks/MockIssuanceAllocator.sol"; /// @notice Targeted tests for uncovered branches in RecurringAgreementManager. contract RecurringAgreementManagerBranchCoverageTest is RecurringAgreementManagerSharedTest { @@ -36,7 +38,7 @@ contract RecurringAgreementManagerBranchCoverageTest is RecurringAgreementManage address(recurringCollector) ) ); - agreementManager.setIssuanceAllocator(address(recurringCollector)); + agreementManager.setIssuanceAllocator(IIssuanceAllocationDistribution(address(recurringCollector))); } /// @notice Setting allocator to an EOA (no code) also fails ERC165 check. @@ -44,7 +46,7 @@ contract RecurringAgreementManagerBranchCoverageTest is RecurringAgreementManage address eoa = makeAddr("randomEOA"); vm.prank(governor); vm.expectRevert(abi.encodeWithSelector(RecurringAgreementManager.InvalidIssuanceAllocator.selector, eoa)); - agreementManager.setIssuanceAllocator(eoa); + agreementManager.setIssuanceAllocator(IIssuanceAllocationDistribution(eoa)); } // ══════════════════════════════════════════════════════════════════════ @@ -219,6 +221,52 @@ contract RecurringAgreementManagerBranchCoverageTest is RecurringAgreementManage // _withdrawAndRebalance — deposit deficit branch (L854/857–862) // ══════════════════════════════════════════════════════════════════════ + // ══════════════════════════════════════════════════════════════════════ + // getIssuanceAllocator — view getter (L281-282) + // ══════════════════════════════════════════════════════════════════════ + + /// @notice getIssuanceAllocator returns the configured allocator and the + /// zero default prior to setIssuanceAllocator. + function test_GetIssuanceAllocator_ReturnsConfiguredValue() public { + assertEq(address(agreementManager.getIssuanceAllocator()), address(0), "Default allocator must be zero"); + + MockIssuanceAllocator allocator = new MockIssuanceAllocator(token, address(agreementManager)); + vm.prank(governor); + agreementManager.setIssuanceAllocator(allocator); + + assertEq( + address(agreementManager.getIssuanceAllocator()), + address(allocator), + "Configured allocator must be returned" + ); + } + + // ══════════════════════════════════════════════════════════════════════ + // offerAgreement — collector returns zero agreementId (L361) + // ══════════════════════════════════════════════════════════════════════ + + /// @notice A conformant collector must return a non-zero agreementId; RAM + /// enforces this invariant with AgreementIdZero. + function test_OfferAgreement_Revert_AgreementIdZero() public { + ZeroIdCollector rogue = new ZeroIdCollector(dataService, address(agreementManager), indexer); + vm.prank(governor); + agreementManager.grantRole(COLLECTOR_ROLE, address(rogue)); + + // Payload content is irrelevant — the mock returns a zero agreementId unconditionally. + IRecurringCollector.RecurringCollectionAgreement memory rca = _makeRCA( + 100 ether, + 1 ether, + 60, + 3600, + uint64(block.timestamp + 365 days) + ); + + token.mint(address(agreementManager), 1_000_000 ether); + vm.prank(operator); + vm.expectRevert(IRecurringAgreementManagement.AgreementIdZero.selector); + agreementManager.offerAgreement(IAgreementCollector(address(rogue)), OFFER_TYPE_NEW, abi.encode(rca)); + } + /// @notice When escrow balance drops below min (after collection), reconcile deposits the deficit. function test_WithdrawAndRebalance_DepositDeficit() public { // Offer agreement in Full mode — escrow gets fully funded @@ -268,3 +316,28 @@ contract RecurringAgreementManagerBranchCoverageTest is RecurringAgreementManage /* solhint-enable graph/func-name-mixedcase */ } + +/// @notice Minimal collector stub that returns a zero agreementId with valid +/// payer/dataService/serviceProvider, used to exercise RAM's AgreementIdZero guard. +contract ZeroIdCollector { + address private immutable _dataService; + address private immutable _payer; + address private immutable _serviceProvider; + + constructor(address dataService_, address payer_, address serviceProvider_) { + _dataService = dataService_; + _payer = payer_; + _serviceProvider = serviceProvider_; + } + + function offer( + uint8 /* offerType */, + bytes calldata /* data */, + uint16 /* options */ + ) external view returns (IAgreementCollector.AgreementDetails memory details) { + details.agreementId = bytes16(0); + details.payer = _payer; + details.dataService = _dataService; + details.serviceProvider = _serviceProvider; + } +} diff --git a/packages/issuance/test/unit/agreement-manager/callbackGas.t.sol b/packages/issuance/test/unit/agreement-manager/callbackGas.t.sol index e4870924f..efe2abce6 100644 --- a/packages/issuance/test/unit/agreement-manager/callbackGas.t.sol +++ b/packages/issuance/test/unit/agreement-manager/callbackGas.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.27; import { IRecurringCollector } from "@graphprotocol/interfaces/contracts/horizon/IRecurringCollector.sol"; +import { IIssuanceAllocationDistribution } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol"; import { RecurringAgreementManagerSharedTest } from "./shared.t.sol"; import { MockIssuanceAllocator } from "./mocks/MockIssuanceAllocator.sol"; @@ -36,7 +37,7 @@ contract RecurringAgreementManagerCallbackGasTest is RecurringAgreementManagerSh vm.label(address(mockAllocator), "MockIssuanceAllocator"); vm.prank(governor); - agreementManager.setIssuanceAllocator(address(mockAllocator)); + agreementManager.setIssuanceAllocator(IIssuanceAllocationDistribution(address(mockAllocator))); } // ==================== beforeCollection gas ==================== diff --git a/packages/issuance/test/unit/agreement-manager/ensureDistributed.t.sol b/packages/issuance/test/unit/agreement-manager/ensureDistributed.t.sol index d84782d37..ec9542977 100644 --- a/packages/issuance/test/unit/agreement-manager/ensureDistributed.t.sol +++ b/packages/issuance/test/unit/agreement-manager/ensureDistributed.t.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.27; import { Vm } from "forge-std/Vm.sol"; import { IIssuanceTarget } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceTarget.sol"; +import { IIssuanceAllocationDistribution } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol"; import { IRecurringCollector } from "@graphprotocol/interfaces/contracts/horizon/IRecurringCollector.sol"; import { RecurringAgreementManager } from "contracts/agreement/RecurringAgreementManager.sol"; @@ -23,7 +24,7 @@ contract RecurringAgreementManagerEnsureDistributedTest is RecurringAgreementMan vm.label(address(mockAllocator), "MockIssuanceAllocator"); vm.prank(governor); - agreementManager.setIssuanceAllocator(address(mockAllocator)); + agreementManager.setIssuanceAllocator(IIssuanceAllocationDistribution(address(mockAllocator))); } // ==================== setIssuanceAllocator ==================== @@ -33,26 +34,26 @@ contract RecurringAgreementManagerEnsureDistributedTest is RecurringAgreementMan vm.prank(governor); vm.expectEmit(address(agreementManager)); - emit IIssuanceTarget.IssuanceAllocatorSet(address(mockAllocator), address(newAllocator)); - agreementManager.setIssuanceAllocator(address(newAllocator)); + emit IIssuanceTarget.IssuanceAllocatorSet(IIssuanceAllocationDistribution(address(mockAllocator)), IIssuanceAllocationDistribution(address(newAllocator))); + agreementManager.setIssuanceAllocator(IIssuanceAllocationDistribution(address(newAllocator))); } function test_SetIssuanceAllocator_Revert_WhenNotGovernor() public { vm.prank(operator); vm.expectRevert(); - agreementManager.setIssuanceAllocator(address(mockAllocator)); + agreementManager.setIssuanceAllocator(IIssuanceAllocationDistribution(address(mockAllocator))); } function test_SetIssuanceAllocator_CanSetToZero() public { vm.prank(governor); - agreementManager.setIssuanceAllocator(address(0)); + agreementManager.setIssuanceAllocator(IIssuanceAllocationDistribution(address(0))); // Should not revert — _ensureIncomingDistributionToCurrentBlock is a no-op with zero address } function test_SetIssuanceAllocator_NoopWhenUnchanged() public { vm.prank(governor); vm.recordLogs(); - agreementManager.setIssuanceAllocator(address(mockAllocator)); + agreementManager.setIssuanceAllocator(IIssuanceAllocationDistribution(address(mockAllocator))); Vm.Log[] memory logs = vm.getRecordedLogs(); assertEq(logs.length, 0, "should not emit when address unchanged"); } @@ -201,7 +202,7 @@ contract RecurringAgreementManagerEnsureDistributedTest is RecurringAgreementMan function test_EnsureDistributed_NoopWhenAllocatorNotSet() public { // Clear allocator vm.prank(governor); - agreementManager.setIssuanceAllocator(address(0)); + agreementManager.setIssuanceAllocator(IIssuanceAllocationDistribution(address(0))); (IRecurringCollector.RecurringCollectionAgreement memory rca, ) = _makeRCAWithId( 100 ether, @@ -309,14 +310,14 @@ contract RecurringAgreementManagerEnsureDistributedTest is RecurringAgreementMan vm.expectRevert( abi.encodeWithSelector(RecurringAgreementManager.InvalidIssuanceAllocator.selector, notAllocator) ); - agreementManager.setIssuanceAllocator(notAllocator); + agreementManager.setIssuanceAllocator(IIssuanceAllocationDistribution(notAllocator)); } function test_SetIssuanceAllocator_Revert_WhenEOA() public { address eoa = makeAddr("eoa"); vm.prank(governor); vm.expectRevert(abi.encodeWithSelector(RecurringAgreementManager.InvalidIssuanceAllocator.selector, eoa)); - agreementManager.setIssuanceAllocator(eoa); + agreementManager.setIssuanceAllocator(IIssuanceAllocationDistribution(eoa)); } // ==================== setIssuanceAllocator switches allocator ==================== @@ -334,7 +335,7 @@ contract RecurringAgreementManagerEnsureDistributedTest is RecurringAgreementMan // Switch allocator MockIssuanceAllocator newAllocator = new MockIssuanceAllocator(token, address(agreementManager)); vm.prank(governor); - agreementManager.setIssuanceAllocator(address(newAllocator)); + agreementManager.setIssuanceAllocator(IIssuanceAllocationDistribution(address(newAllocator))); // Next block: new allocator should be called via _updateEscrow vm.roll(block.number + 1); diff --git a/packages/issuance/test/unit/allocator/distribution.t.sol b/packages/issuance/test/unit/allocator/distribution.t.sol index fb94737de..196317dcf 100644 --- a/packages/issuance/test/unit/allocator/distribution.t.sol +++ b/packages/issuance/test/unit/allocator/distribution.t.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.27; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import { IIssuanceTarget } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceTarget.sol"; +import { IIssuanceAllocationDistribution } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol"; import { TargetIssuancePerBlock, DistributionState, @@ -487,7 +488,7 @@ contract IssuanceAllocatorDistributionTest is IssuanceAllocatorSharedTest { _setIssuanceRate(ISSUANCE_PER_BLOCK); // Set up reentrant target - reentrantTarget.setIssuanceAllocator(address(allocator)); + reentrantTarget.setIssuanceAllocator(IIssuanceAllocationDistribution(address(allocator))); reentrantTarget.setReentrantAction(MockReentrantTarget.ReentrantAction.SetTargetAllocation1Param); // Adding the target should fail due to reentrancy in notification callback diff --git a/packages/issuance/test/unit/allocator/interfaceIdStability.t.sol b/packages/issuance/test/unit/allocator/interfaceIdStability.t.sol index 463416bbd..aee42df80 100644 --- a/packages/issuance/test/unit/allocator/interfaceIdStability.t.sol +++ b/packages/issuance/test/unit/allocator/interfaceIdStability.t.sol @@ -40,7 +40,7 @@ contract AllocateInterfaceIdStabilityTest is Test { // -- DirectAllocation / shared interfaces -- function test_InterfaceId_IIssuanceTarget() public pure { - assertEq(type(IIssuanceTarget).interfaceId, bytes4(0xaee4dc43)); + assertEq(type(IIssuanceTarget).interfaceId, bytes4(0x19f6601a)); } function test_InterfaceId_ISendTokens() public pure { diff --git a/packages/issuance/test/unit/direct-allocation/DirectAllocation.t.sol b/packages/issuance/test/unit/direct-allocation/DirectAllocation.t.sol index 112126a38..d76204091 100644 --- a/packages/issuance/test/unit/direct-allocation/DirectAllocation.t.sol +++ b/packages/issuance/test/unit/direct-allocation/DirectAllocation.t.sol @@ -8,6 +8,7 @@ import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.so import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { IIssuanceAllocationDistribution } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol"; import { IIssuanceTarget } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceTarget.sol"; import { ISendTokens } from "@graphprotocol/interfaces/contracts/issuance/allocate/ISendTokens.sol"; @@ -15,6 +16,26 @@ import { BaseUpgradeable } from "../../../contracts/common/BaseUpgradeable.sol"; import { IGraphToken } from "../../../contracts/common/IGraphToken.sol"; import { DirectAllocation } from "../../../contracts/allocate/DirectAllocation.sol"; import { MockGraphToken } from "../mocks/MockGraphToken.sol"; +import { TargetIssuancePerBlock } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocatorTypes.sol"; + +/// @notice Minimal IIssuanceAllocationDistribution stub that advertises the interface via ERC-165. +/// Used to exercise DirectAllocation's ERC-165 acceptance path without pulling in heavier +/// allocator mocks from other test trees. +contract StubIssuanceAllocator is IIssuanceAllocationDistribution, IERC165 { + function distributeIssuance() external pure override returns (uint256) { + return 0; + } + + function getTargetIssuancePerBlock(address) external pure override returns (TargetIssuancePerBlock memory) { + return TargetIssuancePerBlock(0, 0, 0, 0); + } + + function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { + return + interfaceId == type(IIssuanceAllocationDistribution).interfaceId || + interfaceId == type(IERC165).interfaceId; + } +} /// @notice Tests for DirectAllocation contract. contract DirectAllocationTest is Test { @@ -133,15 +154,81 @@ contract DirectAllocationTest is Test { directAlloc.beforeIssuanceAllocationChange(); } - function test_SetIssuanceAllocator_NoOp() public { + function test_GetIssuanceAllocator_InitiallyZero() public view { + assertEq(address(directAlloc.getIssuanceAllocator()), address(0)); + } + + function test_SetIssuanceAllocator_UpdatesGetter() public { + StubIssuanceAllocator allocator = new StubIssuanceAllocator(); + vm.prank(governor); + directAlloc.setIssuanceAllocator(allocator); + assertEq(address(directAlloc.getIssuanceAllocator()), address(allocator)); + } + + function test_SetIssuanceAllocator_EmitsEvent() public { + StubIssuanceAllocator allocator = new StubIssuanceAllocator(); + vm.prank(governor); + vm.expectEmit(address(directAlloc)); + emit IIssuanceTarget.IssuanceAllocatorSet(IIssuanceAllocationDistribution(address(0)), allocator); + directAlloc.setIssuanceAllocator(allocator); + } + + function test_SetIssuanceAllocator_EmitsEventWithOldValue() public { + StubIssuanceAllocator first = new StubIssuanceAllocator(); + StubIssuanceAllocator second = new StubIssuanceAllocator(); + vm.prank(governor); + directAlloc.setIssuanceAllocator(first); + + vm.prank(governor); + vm.expectEmit(address(directAlloc)); + emit IIssuanceTarget.IssuanceAllocatorSet(first, second); + directAlloc.setIssuanceAllocator(second); + } + + function test_SetIssuanceAllocator_SkipsWhenSameValue() public { + StubIssuanceAllocator allocator = new StubIssuanceAllocator(); + vm.prank(governor); + directAlloc.setIssuanceAllocator(allocator); + + vm.prank(governor); + vm.recordLogs(); + directAlloc.setIssuanceAllocator(allocator); + assertEq(vm.getRecordedLogs().length, 0); + } + + function test_SetIssuanceAllocator_AllowsZeroAddress() public { + // Zero-address bypasses the ERC165 check — clearing the allocator is always legal. + StubIssuanceAllocator allocator = new StubIssuanceAllocator(); + vm.prank(governor); + directAlloc.setIssuanceAllocator(allocator); + + vm.prank(governor); + directAlloc.setIssuanceAllocator(IIssuanceAllocationDistribution(address(0))); + assertEq(address(directAlloc.getIssuanceAllocator()), address(0)); + } + + /// @notice An EOA (no code) fails the ERC-165 interface probe and must be rejected. Prevents + /// governance from accidentally wiring up a non-contract as the allocator. + function test_Revert_SetIssuanceAllocator_WhenEOA() public { + address eoa = makeAddr("eoa"); + vm.prank(governor); + vm.expectRevert(abi.encodeWithSelector(DirectAllocation.InvalidIssuanceAllocator.selector, eoa)); + directAlloc.setIssuanceAllocator(IIssuanceAllocationDistribution(eoa)); + } + + /// @notice A contract that does not implement IIssuanceAllocationDistribution must be rejected. + /// Uses the MockGraphToken fixture — it has code but doesn't advertise the allocator interface. + function test_Revert_SetIssuanceAllocator_WhenWrongInterface() public { vm.prank(governor); - directAlloc.setIssuanceAllocator(makeAddr("allocator")); + vm.expectRevert(abi.encodeWithSelector(DirectAllocation.InvalidIssuanceAllocator.selector, address(token))); + directAlloc.setIssuanceAllocator(IIssuanceAllocationDistribution(address(token))); } function test_Revert_SetIssuanceAllocator_NonGovernor() public { + StubIssuanceAllocator allocator = new StubIssuanceAllocator(); vm.expectRevert(); vm.prank(unauthorized); - directAlloc.setIssuanceAllocator(makeAddr("allocator")); + directAlloc.setIssuanceAllocator(allocator); } // ==================== ERC-165 Interface Support ==================== diff --git a/packages/testing/test/harness/FullStackHarness.t.sol b/packages/testing/test/harness/FullStackHarness.t.sol index 842ebe1a1..d095804f0 100644 --- a/packages/testing/test/harness/FullStackHarness.t.sol +++ b/packages/testing/test/harness/FullStackHarness.t.sol @@ -27,6 +27,7 @@ import { OFFER_TYPE_NEW } from "@graphprotocol/interfaces/contracts/horizon/IAgreementCollector.sol"; import { IIssuanceTarget } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceTarget.sol"; +import { IIssuanceAllocationDistribution } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol"; import { IGraphToken as IssuanceIGraphToken } from "issuance/common/IGraphToken.sol"; import { IIndexingAgreement } from "@graphprotocol/interfaces/contracts/subgraph-service/internal/IIndexingAgreement.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; @@ -291,7 +292,7 @@ abstract contract FullStackHarness is Test { ram.grantRole(OPERATOR_ROLE, operator); ram.grantRole(DATA_SERVICE_ROLE, address(subgraphService)); ram.grantRole(COLLECTOR_ROLE, address(recurringCollector)); - ram.setIssuanceAllocator(address(issuanceAllocator)); + ram.setIssuanceAllocator(IIssuanceAllocationDistribution(address(issuanceAllocator))); issuanceAllocator.setIssuancePerBlock(1 ether); issuanceAllocator.setTargetAllocation(IIssuanceTarget(address(ram)), 1 ether); diff --git a/packages/testing/test/harness/RealStackHarness.t.sol b/packages/testing/test/harness/RealStackHarness.t.sol index db99ace6c..1d7cf6bcd 100644 --- a/packages/testing/test/harness/RealStackHarness.t.sol +++ b/packages/testing/test/harness/RealStackHarness.t.sol @@ -9,6 +9,7 @@ import { RecurringCollector } from "horizon/payments/collectors/RecurringCollect import { IssuanceAllocator } from "issuance/allocate/IssuanceAllocator.sol"; import { RecurringAgreementManager } from "issuance/agreement/RecurringAgreementManager.sol"; import { IIssuanceTarget } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceTarget.sol"; +import { IIssuanceAllocationDistribution } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol"; // Use the issuance IGraphToken for RAM/allocator (IERC20 + mint) import { IGraphToken as IssuanceIGraphToken } from "issuance/common/IGraphToken.sol"; @@ -123,7 +124,7 @@ abstract contract RealStackHarness is Test { ram.grantRole(OPERATOR_ROLE, operator); ram.grantRole(DATA_SERVICE_ROLE, dataService); ram.grantRole(COLLECTOR_ROLE, address(recurringCollector)); - ram.setIssuanceAllocator(address(issuanceAllocator)); + ram.setIssuanceAllocator(IIssuanceAllocationDistribution(address(issuanceAllocator))); // Configure allocator: set total issuance rate, then allocate to RAM issuanceAllocator.setIssuancePerBlock(1 ether); issuanceAllocator.setTargetAllocation(IIssuanceTarget(address(ram)), 1 ether);