Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
144 commits
Select commit Hold shift + click to select a range
a7fb875
feat: implement Indexing Agreements
matiasedgeandnode May 23, 2025
1651716
test: Add extra tests for Indexing Agreements
matiasedgeandnode Jun 16, 2025
b53ca01
fix: [TRST-H-1] IndexingAgreement.collect() on CanceledByPayer
matiasedgeandnode Jun 19, 2025
da42fb4
fix: Remove PaymentType constraint from RecurringCollector
matiasedgeandnode Jun 23, 2025
7695c9e
fix: [TRST-H-2] Only agreement owner can collect indexing fee
matiasedgeandnode Jul 21, 2025
8048c4c
fix: [TRST-H-3] collect() checks provision
matiasedgeandnode Jul 22, 2025
29dfdcc
fix: [TRST-M-1] correct TYPEHASH string for RCAU
matiasedgeandnode Jul 22, 2025
345cfc8
fix: [TRST-M-2] shared collection window logic
matiasedgeandnode Jul 23, 2025
8b2e93a
fix: [TRST-M-3] Add nonce-based replay protection
matiasedgeandnode Jul 23, 2025
aac9f8b
fix: [TRST-L-3] Add deterministic agreement ID
matiasedgeandnode Jul 23, 2025
836c0c2
fix: [TRST-L-5] Add slippage protection
matiasedgeandnode Jul 24, 2025
e3d2787
fix: [TRST-L-6] Proper agreement version check
matiasedgeandnode Jul 24, 2025
308d6e6
document: [TRST-L-7] update()
matiasedgeandnode Jul 24, 2025
17b794e
fix: [TRST-L-9] Cancel agreement if over-allocated
matiasedgeandnode Jul 24, 2025
6d9a18c
fix: [TRST-R-1] minor fixes
matiasedgeandnode Jul 24, 2025
58b7a28
fix: [TRST-R-4] CEI violation
matiasedgeandnode Jul 24, 2025
5f732ac
fix: [TRST-R-5] Terms validation
matiasedgeandnode Jul 24, 2025
b492251
fix: [TRST-R-6] Configurable indexing fees cut
matiasedgeandnode Jul 25, 2025
0e469be
fix: [TRST-M-2] improve _getCollectionInfo()
matiasedgeandnode Aug 11, 2025
f65877e
feat(interfaces): remove staking extension from interfaces
tmigone Nov 28, 2025
714b32c
chore: more interface removal
tmigone Nov 28, 2025
392047c
feat: clean up post horizon on horizon contracts
tmigone Nov 28, 2025
28f2661
test: update horizon tests after cleanup
tmigone Nov 28, 2025
1d2b76d
fix: more cleanup on horizon, interfaces and toolshed packages
tmigone Dec 1, 2025
ac103d7
fix: contracts changes
tmigone Dec 1, 2025
c541c8d
feat: remove stuff from subgraph service contracts
tmigone Dec 1, 2025
abcafaa
chore: remove dead code
tmigone Dec 1, 2025
eb59951
Merge branch 'main' into tmigone/horizon-cleanup
tmigone Dec 1, 2025
a2abc90
test(contract): hack outdated test suite to pass
tmigone Dec 1, 2025
bee3e79
test: fix vm.assume too many rejections
tmigone Dec 1, 2025
c419850
Merge branch 'main' into tmigone/horizon-cleanup
tmigone Dec 1, 2025
a5bbbf8
chore: address review feedback
tmigone Dec 2, 2025
da2dba7
feat: add fn to force withdraw legacy stake and delegation
tmigone Dec 3, 2025
dd8d624
chore: lint
tmigone Dec 3, 2025
0f71288
fix: re-validate thawingPeriod when accepting provision parameters (O…
tmigone Dec 19, 2025
a91f59a
fix: return correct result for getThawedTokens when called for delega…
tmigone Dec 19, 2025
ed356fe
fix: remove more deprecated code (OZ N-01)
tmigone Dec 19, 2025
1c3e306
fix: outdated documentation (OZ N-02)
tmigone Dec 19, 2025
d9f053a
test: fix tests
tmigone Dec 19, 2025
5c51f0d
chore: use ^0.8.27 caret pragma and bump solc to 0.8.34
RembrandtK Feb 26, 2026
bf6d4cb
Merge commit '0e469beeba0ec433e313be8c9129bcf99acdaac6' into indexing…
RembrandtK Mar 1, 2026
28edcd7
Merge commit 'd9f053a7d96a8a4d81415303ae1d537f836f887c' into indexing…
RembrandtK Mar 1, 2026
f4451f1
feat: add back legacy allocation id collision check
tmigone Feb 23, 2026
fd96234
chore: restore pragma
RembrandtK Mar 1, 2026
fa99514
fix: cap maxSecondsPerCollection instead of reverting
RembrandtK Feb 27, 2026
c6836a7
fix: enforce temporal validation on zero-token collections and remove…
RembrandtK Feb 26, 2026
8efaec9
feat: add adjustThaw to PaymentsEscrow
RembrandtK Feb 27, 2026
3f1578c
refactor: rename IRewardsEligibility to IProviderEligibility
RembrandtK Mar 1, 2026
d20bc84
feat: contract approver model for RecurringCollector accept/update
RembrandtK Feb 27, 2026
ec72360
feat: IDataServiceAgreements interface and SubgraphService integration
RembrandtK Feb 27, 2026
89def3d
feat: enumerable indexer tracking for REO and issuance constructor cl…
RembrandtK Mar 1, 2026
a23ad68
feat: RecurringAgreementManager with lifecycle, escrow funding, and a…
RembrandtK Mar 1, 2026
8673c34
fix(rewards): reorder subtraction in _updateSubgraphRewards to avoid …
RembrandtK Mar 2, 2026
506601f
fix(test): set subgraphService in snapshot inversion tests
RembrandtK Mar 3, 2026
32bd361
fix(test): exclude named test users from fuzz-generated indexer addre…
RembrandtK Mar 3, 2026
0f4f486
feat: add issuance distribution integration to RAM
RembrandtK Mar 10, 2026
86a5d6e
docs: clarify two-layer token capping semantics in collection flow
RembrandtK Mar 10, 2026
7405c9d
docs: add payments trust model
RembrandtK Mar 10, 2026
9ae7643
test: add cross-package testing harness with callback gas measurements
RembrandtK Mar 20, 2026
efc5116
docs(audit): add PR1301 audit report and findings
RembrandtK Mar 20, 2026
956d983
feat(RAM): threshold-based escrow basis degradation (TRST-M-2, TRST-M-3)
RembrandtK Mar 28, 2026
e1d73c1
fix(RAM): refresh escrow snapshot in _updateEscrow (TRST-H-3)
RembrandtK Mar 28, 2026
e1a3c5a
fix(RAM): add minimum thaw fraction to prevent dust-thaw griefing (TR…
RembrandtK Mar 28, 2026
56322cc
feat(RM): add revert control for ineligible indexers
RembrandtK Mar 27, 2026
3b617b4
docs(audit): acknowledge audit findings (TRST-CR-1/3, L-4, R-1, SR-1/…
RembrandtK Mar 28, 2026
df93851
feat: resize allocations to zero instead of force-closing
RembrandtK Mar 30, 2026
b124656
feat: revert closing allocations with active indexing agreement
RembrandtK Mar 30, 2026
40c9104
fix(collector): reject agreements with overflow-prone token/duration …
RembrandtK Apr 1, 2026
83e2515
feat(collector): offer storage, stored-hash auth, scoped claims and c…
RembrandtK Mar 31, 2026
38b090c
fix(collector): harden payer callbacks, add opt-in eligibility gate (…
RembrandtK Mar 31, 2026
5b41005
fix: compiler stack overflow
RembrandtK Apr 1, 2026
608346e
refactor(RAM): replace set-based range views with indexed accessors
RembrandtK Apr 1, 2026
0b22a14
feat(RAM): add emergency role control and eligibility oracle escape h…
RembrandtK Apr 1, 2026
77fc87f
refactor(RAM): convert offerAgreement and cancelAgreement to IAgreeme…
RembrandtK Apr 1, 2026
64bc0f0
refactor(RAM): remove offerAgreementUpdate, revokeAgreementUpdate, an…
RembrandtK Apr 1, 2026
daf0b47
refactor(RAM): restructure storage into collector → provider hierarchy
RembrandtK Apr 1, 2026
9ec2c07
feat(collector): make RecurringCollector upgradeable
RembrandtK Apr 1, 2026
bbe0195
feat(collector): add pause mechanism to RecurringCollector (TRST-L-3)
RembrandtK Apr 1, 2026
0bbb476
fix(subgraph-service): remove VALID_PROVISION and REGISTERED from can…
RembrandtK Apr 2, 2026
3bac236
feat(contracts): add getIssuanceAllocator to IIssuanceTarget interface
RembrandtK Apr 10, 2026
3185a94
Merge branch 'indexing-payments-management-audit-fix-reduced-wip' int…
RembrandtK Apr 10, 2026
a1af8b6
refactor(data-edge): upgrade to ethers v6 and @nomicfoundation plugins
RembrandtK Apr 10, 2026
1ee3f26
fix: horizon test and token-distribution compatibility fixes
RembrandtK Apr 10, 2026
4ef9bec
chore: update addresses, contract registries, and MockREO
RembrandtK Apr 10, 2026
fa69c7a
docs: REO testing plans and rewards behaviour documentation
RembrandtK Apr 10, 2026
2cdb0d0
feat(deployment): GIP-0088 deployment infrastructure
RembrandtK Apr 10, 2026
df9a846
docs: update audit extracts for PR1301 v02 report
RembrandtK Apr 17, 2026
322d613
chore: update arbitrumSepolia addresses and deployment metadata
RembrandtK Apr 10, 2026
0a29330
fix(deployment): register governor as rocketh named account for local…
RembrandtK Apr 10, 2026
70366d0
fix(ignition): update HorizonStaking and SubgraphService modules for …
RembrandtK Apr 10, 2026
6114cde
docs: fix broken badge and link
operagxoksana Mar 5, 2026
96e504d
Merge branch 'main' into reo-deployment-3
RembrandtK Apr 23, 2026
60498e9
chore(tag-deployment): drop commit sha from tag annotation
RembrandtK Apr 24, 2026
cb6c45c
fix(collector): add gas overhead buffer to callback prechecks (TRST-L-9)
RembrandtK Apr 17, 2026
3ce5813
fix(collector): cap returndata copy in payer callbacks (TRST-M-4)
RembrandtK Apr 17, 2026
8e50abd
docs: add response to TRST-L-10 EIP-7702 callback dispatch (won't fix)
RembrandtK Apr 17, 2026
6a0ac79
feat(RAM): drop pair tracking below residual escrow threshold (TRST-M…
RembrandtK Apr 17, 2026
f96a731
docs: add responses to TRST-L-6, TRST-R-7 (both won't fix)
RembrandtK Apr 19, 2026
35447e7
docs(audit): acknowledge TRST-R-3 cancelAgreement defensive check
RembrandtK Apr 20, 2026
2dd2372
fix(collector): remove dead oldHash guard (TRST-R-6)
RembrandtK Apr 20, 2026
c1ef1cb
fix(collector): non-zero offer types, reserve OFFER_TYPE_NONE=0 senti…
RembrandtK Apr 19, 2026
3621793
refactor(interfaces): drop unused state and offer-option flags, tight…
RembrandtK Apr 19, 2026
f32e550
docs(audit): acknowledge trust-boundary correction in TRST-H-4
RembrandtK Apr 19, 2026
d2fd364
docs(audit): acknowledge reclaim-reason change in TRST-R-13
RembrandtK Apr 19, 2026
b61d441
docs(ram): document collector replay-protection assumption (TRST-R-4)
RembrandtK Apr 19, 2026
0271015
docs(ram): document non-retroactive role-change semantics (TRST-R-10)
RembrandtK Apr 19, 2026
1ee49f2
docs(ram): align pause-escalation prose with whenNotPaused scope (TRS…
RembrandtK Apr 19, 2026
9396dbd
docs(collector): note self-authorization auth-check obligation (TRST-…
RembrandtK Apr 19, 2026
1e5a6b3
fix(subgraph-service): validate update terms against RCAU rate, not s…
RembrandtK Apr 21, 2026
8be1aa0
refactor(collector): preparatory helpers, signatures, and version con…
RembrandtK Apr 22, 2026
cfaf39b
refactor(collector): drop unreachable agreementId-zero check
RembrandtK Apr 21, 2026
35748ff
refactor(collector): extract _requireValidTerms from duplicated valid…
RembrandtK Apr 21, 2026
0ad0be4
refactor(collector): split accept logic out of _validateAndStoreAgree…
RembrandtK Apr 21, 2026
bfe7754
refactor(collector): split update apply out of _validateAndStoreUpdate
RembrandtK Apr 21, 2026
594d19b
feat(subgraph-service): idempotent accept/update with allocation rebi…
RembrandtK Apr 27, 2026
885555e
refactor(collector): hoist solhint-disable, idiomatic deadline compar…
RembrandtK Apr 27, 2026
572853b
fix(collector): validate offer terms against deadline, not block.time…
RembrandtK Apr 22, 2026
b6adbf1
refactor(collector): extract _getAgreementDetails/_versionHashAt helpers
RembrandtK Apr 27, 2026
8b48437
fix(collector): persistent agreement.payer for independent cancellati…
RembrandtK Apr 27, 2026
769b252
feat(collector): idempotent accept/update/cancel-on-nothing
RembrandtK Apr 27, 2026
f96b4ea
feat(collector): add OfferCancelled event for SCOPE_PENDING cancellat…
RembrandtK Apr 25, 2026
c1dfc34
feat(collector): per-version semantics in getAgreementDetails (TRST-L…
RembrandtK Apr 27, 2026
33d2ced
feat(collector): compose cancel/settled flags in getAgreementDetails …
RembrandtK Apr 19, 2026
fe13b11
feat(collector): add SCOPE_SIGNED to cancel() for EOA offer revocatio…
RembrandtK Apr 19, 2026
b13d910
feat(issuance): expose getIssuanceAllocator on IIssuanceTarget
RembrandtK Apr 10, 2026
e4cd9e0
fix(collector): validate full terms at offer time
RembrandtK Apr 26, 2026
6772545
fix(collector): respect deadlines in scoped claim cap
RembrandtK Apr 26, 2026
067168e
refactor(collector): collapse redundant state guard in _getMaxNextClaim
RembrandtK Apr 27, 2026
757da41
fix(collector): use dedicated error for invalid offer type in offer()
RembrandtK Apr 27, 2026
4e4ad4b
Merge branch 'reo-deployment-3' into deployment/testnet-dips/2024-04-…
RembrandtK Apr 27, 2026
048971b
feat(deployment): drive RM revertOnIneligible from network config
RembrandtK Apr 28, 2026
2c06233
chore(deployment): remove vestigial RecurringCollector block from net…
RembrandtK Apr 28, 2026
5843b6d
refactor(deployment): centralise config resolution with ResolvedSettings
RembrandtK Apr 28, 2026
71e567f
feat(deployment): bundle RM.setRevertOnIneligible into upgrade govern…
RembrandtK Apr 28, 2026
2828d64
fix(deployment): gate sync rocketh-record seeding on artifact verific…
RembrandtK Apr 28, 2026
26c13c8
test(deployment): cover shouldSeedRocketh gate truth table
RembrandtK Apr 28, 2026
95965d6
ci(publish): add interfaces/toolshed, dry-run, and release tagging
RembrandtK Apr 24, 2026
2b54206
ci(publish): switch to OIDC trusted publishing
RembrandtK Apr 24, 2026
4136034
ci(publish): upgrade npm before OIDC publish for trusted-publisher auth
RembrandtK Apr 24, 2026
248c000
ci(setup): bump pnpm from 10.17.0 to 10.28.0 to match packageManager
RembrandtK Apr 24, 2026
4e0262a
ci(publish): pin npm to 11.13.0 instead of latest
RembrandtK Apr 25, 2026
af3acd7
ci: bump actions to latest stable and switch to Node 24
RembrandtK Apr 28, 2026
5affcc4
ci(publish): drop explicit npm upgrade step
RembrandtK Apr 28, 2026
5660ec0
Merge branch 'main' into deployment/testnet-dips/2024-04-28/audit-fix-2
RembrandtK Apr 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 3 additions & 2 deletions .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
name: Setup
description: Install system deps, Foundry, Node.js, pnpm, and the workspace's dependencies.

runs:
using: composite
Expand All @@ -15,9 +16,9 @@ runs:
shell: bash
run: corepack enable
- name: Install Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
cache: 'pnpm'
- name: Set up pnpm via Corepack
shell: bash
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: recursive

Expand All @@ -40,7 +40,7 @@ jobs:

- name: Upload coverage reports
if: steps.coverage_files.outputs.files != ''
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v6
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ${{ steps.coverage_files.outputs.files }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0 # Needed to get all history for comparing changes

Expand Down
7 changes: 1 addition & 6 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,11 @@ jobs:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: recursive
- name: Set up environment
uses: ./.github/actions/setup
- name: Upgrade npm for OIDC trusted publishing
# pnpm publish delegates registry auth to the underlying npm CLI.
# OIDC trusted publishing requires npm >= 11.5.1; pinned to a known-good
# version for reproducibility — safe to bump as long as it stays >= 11.5.1.
run: npm install -g npm@11.13.0
- name: Read package info
id: pkg
shell: bash
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/require-audit-label.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
steps:
- name: Get changed files
id: changed
uses: actions/github-script@v7
uses: actions/github-script@v9
with:
script: |
const { data: files } = await github.rest.pulls.listFiles({
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/verifydeployed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v6
with:
submodules: recursive
- name: Set up environment
Expand All @@ -36,7 +36,7 @@ jobs:
pnpm build

- name: Save build artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v7
with:
name: contract-artifacts
path: |
Expand All @@ -49,15 +49,15 @@ jobs:
needs: build
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v6
- name: Set up environment
uses: ./.github/actions/setup
- name: Build
run: |
pushd packages/contracts
pnpm build || pnpm build
- name: Get build artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v8
with:
name: contract-artifacts

Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ packages/*/.eslintcache
dist/
dist-v5/
build/
packages/contracts/**/types/
deployments/hardhat/
*.js.map
*.d.ts.map
Expand Down Expand Up @@ -58,7 +59,9 @@ bin/
.env
.DS_Store
.vscode
core
# Forge core dumps
**/core
!**/core/

# Coverage and other reports
coverage/
Expand Down
176 changes: 176 additions & 0 deletions docs/PaymentsTrustModel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# Payments Trust Model

This document describes the trust assumptions between the five core actors in the Graph Horizon payments protocol: **payer**, **collector**, **data service**, **receiver**, and **escrow**. The general model is described first, followed by specifics of the current implementation (RecurringCollector, SubgraphService, RAM).

## Trust Summary

| Relationship | Trust | Mitigation |
| --------------------------- | ----------------------------------------- | ------------------------------------------------ |
| Payer → Collector | Enforces agreed caps | Protocol-deployed; escrow caps absolute exposure |
| Payer → Receiver | Claimed work is honest | Post-hoc disputes + stake locking |
| Receiver → Payer (EOA) | Escrow stays funded | Thaw period; on-chain visibility |
| Receiver → Payer (contract) | Escrow stays funded; not block collection | RecurringAgreementManager: protocol-deployed |
| Receiver → Collector | Correctly caps and forwards payment | Protocol-deployed; code is transparent |
| Receiver → Data Service | Correct computation; not paused | Protocol-deployed; code is transparent |
| Receiver → Escrow | Releases funds on valid collection | Stateless; no discretionary logic |
| Data Service ↔ Collector | Each trusts the other's domain | Two-layer capping; independent validation |

## Actors

| Actor | Role | Examples |
| ---------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| **Payer** | Funds escrow; authorizes collector contracts | RecurringAgreementManager (protocol-managed), external payer (ECDSA-signed) |
| **Collector** | Validates payment requests; enforces per-agreement caps | RecurringCollector |
| **Data service** | Entry point for collection; computes amounts earned | SubgraphService |
| **Receiver** | Service provider receiving payment | Indexer |
| **Escrow** | Holds GRT per (payer, collector, receiver) tuple; enforces thaw periods | PaymentsEscrow |

## Payment Flow (General Model)

```
│ Receiver
└─> Data Service.collect(work done)
└─> Collector.collect(tokens earned)
│ validates payment terms, caps amount
└─> PaymentsEscrow.collect(tokens to collect)
└─> GraphPayments.collect(tokens collected)
│ distributes to: protocol (burned), data service, delegation pool, receiver
<───┘
<───┘
<───┘
<───┘
```

Any data service and collector can plug into this flow. The PaymentsEscrow and GraphPayments layers are fixed protocol infrastructure. The data service computes its own token amount; the collector independently caps it; the actual payment is `min(tokens earned, agreement cap)`, and escrow reverts if balance is insufficient.

### RecurringCollector Extensions

RecurringCollector adds payer callbacks when the payer is a contract:

```
│ Receiver
└─> Data Service.collect(work done)
└─> RecurringCollector.collect(tokens earned)
│ validates agreement terms, caps amount
│ validates receiver has active provision with data service
│ if 0 < tokensToCollect AND payer is contract:
│ if implements IProviderEligibility:
│ require payer.isEligible(receiver) ← can BLOCK
│ try payer.beforeCollection(id, tokens) (can't block)
└─> PaymentsEscrow.collect(tokens to collect)
└─> GraphPayments.collect(tokens collected)
│ distributes to: protocol (burned), data service, delegation pool, receiver
<───┘
<───┘
│ if payer is contract: (even if tokensToCollect == 0)
│ try payer.afterCollection(id, tokens) (can't block)
<───┘
<───┘
```

- **`isEligible`**: fail-open gate — only an explicit return of `0` blocks collection; call failures (reverts, malformed data) are ignored to prevent a buggy payer from griefing the receiver. Only called when `0 < tokensToCollect`.
- **`beforeCollection`**: try-catch — allows payer to top up escrow (RAM uses this for JIT deposits), but cannot block (though a malicious contract payer could consume excessive gas). Only called when `0 < tokensToCollect`.
- **`afterCollection`**: try-catch — allows payer to reconcile state post-collection, cannot block (same gas exhaustion caveat). Called even when `tokensToCollect == 0` (zero-token collections still trigger reconciliation).

## Trust Relationships

### Payer → Collector

**Trust required**: The payer authorizes the collector contract and trusts it to enforce payment terms; that it will not collect more than the agreed-upon amounts per collection period.

**Mitigation**: The collector is a protocol-deployed contract with fixed logic. The escrow balance provides an absolute ceiling — the collector cannot extract more than the deposited balance.

> _RecurringCollector_: enforces per-agreement caps of `maxOngoingTokensPerSecond × maxSecondsPerCollection` (plus `maxInitialTokens` on first collection) per collection window. The payer's exposure is bounded by the agreement terms they signed or authorized.

### Payer → Receiver

**Trust required**: The receiver is paid immediately when collecting based on claimed work done. The payer relies on post-hoc enforcement rather than on-chain validation of the receiver's claims.

**Mitigation**: The payment protocol itself is agnostic to what evidence the receiver provides — that is the data service's domain.

> _SubgraphService_: the receiver submits a POI (Proof of Indexing) which is emitted in events but not validated on-chain. Payment proceeds regardless of POI correctness. The dispute system provides post-hoc enforcement: fishermen can challenge invalid POIs, and the indexer's locked stake (`tokensCollected × stakeToFeesRatio`) serves as economic collateral during the dispute period.
>
> _RAM as payer_: the payer is the protocol itself, and if configured, an eligibility oracle gates the receiver's ability to collect (checked by RecurringCollector via `IProviderEligibility`).

### Receiver → Payer

**Trust minimised by escrow**: The escrow is the primary trust-minimisation mechanism — to avoid trust in the payer, the receiver should bound uncollected work to what the escrow guarantees rather than relying on the payer to top up.

Caveats on effective escrow (contract payers introduce additional trust requirements — see caveat 3):

1. **Thawing reduces effective balance** — a payer can initiate a thaw; once the thaw period completes, those tokens are withdrawable. The receiver should account for the thawing period and any in-progress thaws when assessing available escrow.
2. **Cancellation freezes the collection window** at `canceledAt` — the receiver can still collect for the period up to cancellation (with `minSecondsPerCollection` bypassed), but no further.
3. **Contract payers can block** — if the payer is a contract that implements `IProviderEligibility`, it can deny collection via `isEligible` (see [RecurringCollector Extensions](#recurringcollector-extensions)).

**Mitigation**: The thawing period provides a window for the receiver to collect before funds are withdrawn. The escrow balance and thaw state are publicly visible on-chain.

> _RAM as payer_: RAM automates escrow maintenance (Full/OnDemand/JIT modes). When not operating in Full escrow mode, the receiver also depends on RAM's ability to fund at collection time. Mitigation: RAM is a protocol-deployed contract — its funding logic is transparent and predictable, with no adversarial incentive to deny payment.

### Receiver → Data Service

**Trust required**: The receiver (or their operator) calls the data service's `collect()` directly. The receiver trusts it to:

1. **Compute amounts correctly** — the data service determines its claim of what is earned
2. **Not be paused** — the data service may have a pause mechanism that would block collection

**Mitigation**: The data service is a protocol-deployed contract. Token amounts are capped by the collector independently, so data service overstatement is bounded.

> _SubgraphService_: `_tokensToCollect` computes the amount earned. The `enforceService` modifier requires the caller to be authorized by the receiver (indexer) for their provision.

### Receiver → Escrow

**Trust required**: The receiver trusts escrow to release funds when a valid collection is presented. The receiver has no direct access to escrow — funds can only flow through the authorized collection path (data service → collector → escrow → GraphPayments → receiver).

**Mitigation**: Escrow is a stateless intermediary — it debits the payer's balance and forwards to GraphPayments. No discretionary logic. The failure modes are insufficient balance or protocol-wide pause (escrow's `collect` has a `notPaused` modifier).

### Data Service → Collector

**Trust required**: The data service trusts the collector to faithfully enforce temporal and amount-based caps. The data service provides its own token calculation, but the collector applies `min(requested, cap)` — the data service relies on this capping being correct.

**Mitigation**: Both are protocol-deployed contracts. The two-layer capping model means neither layer alone determines the payout — the minimum of both applies.

### Collector → Data Service

**Trust required**: The collector trusts the data service to call `collect()` only with valid, legitimate payment requests. The collector validates payment terms but relies on the data service to verify service delivery.

**Mitigation**: The collector validates its own domain (agreement existence, temporal bounds, amount caps) independently.

> _RecurringCollector + SubgraphService_: the collector validates RCA terms; the data service verifies allocation status and emits POIs for dispute.

## Who Can Block Collection?

Which actors can prevent a collection from succeeding, and how:

| Actor | Can block? | How (general model) |
| ------------ | ---------- | ---------------------------------------------- |
| Payer | Yes | Contract payer only, via `isEligible` |
| Collector | Yes | Reject payment request based on its own rules |
| Data service | Yes | Pause mechanism; code-level revert conditions |
| Receiver | No | Can only initiate, not block |
| Escrow | Yes | Insufficient balance; also protocol-wide pause |

### Implementation-Specific Notes

**ECDSA-signed agreements** (external payer): the payer is an EOA and has no on-chain blocking mechanism. The receiver's trust is bounded by the current escrow balance (minus any thawing amount).

**RAM-managed agreements** (protocol payer): the payer (RAM) has no adversarial incentive to block. If an eligibility oracle is configured, blocking trust effectively transfers to the oracle (see [RecurringCollector Extensions](#recurringcollector-extensions)).

## Trust Reduction Mechanisms

| Mechanism | What it bounds | Actor protected | Scope |
| --------------------------------------------------------------- | ------------------------------------------------------------------ | --------------- | ------------------------ |
| Escrow deposit + thaw period | Payer can't instantly withdraw | Receiver | General |
| Two-layer token capping | Neither data service nor collector alone sets amount | Payer | General |
| Collector-enforced agreement terms | Per-collection exposure | Payer | General |
| Cancellation still allows final collection | Receiver collects accrued amount | Receiver | General |
| Dispute system + stake locking | Invalid POIs are challengeable | Payer / network | SubgraphService |
| Eligibility oracle | Ineligible receivers denied | Payer | RecurringCollector + RAM |
| `lastCollectionAt` advancing only through validated collections | No fake liveness signals (advances even on zero-token collections) | All | RecurringCollector |

## Related Documents

- [MaxSecondsPerCollectionCap.md](../packages/horizon/contracts/payments/collectors/MaxSecondsPerCollectionCap.md) — Two-layer capping semantics
- [RecurringAgreementManager.md](../packages/issuance/contracts/agreement/RecurringAgreementManager.md) — RAM escrow management
- [RewardsEligibilityOracle.md](../packages/issuance/contracts/eligibility/RewardsEligibilityOracle.md) — Oracle trust model and failsafe
- [RewardAccountingSafety.md](./RewardAccountingSafety.md) — Reward accounting invariants
- [RewardConditions.md](./RewardConditions.md) — Reclaim conditions
Loading
Loading