Indexing payments management audit fixed rebased#1331
Indexing payments management audit fixed rebased#1331RembrandtK wants to merge 128 commits intomainfrom
Conversation
Fixes TRST-M-1 audit finding: Wrong TYPEHASH string is used for agreement updates, limiting functionality. * Fixed EIP712_RCAU_TYPEHASH to use correct uint64 types for deadline and endsAt fields (was incorrectly using uint256) * This prevents signature verification failures for RecurringCollectionAgreementUpdate
Fixes TRST-M-2 audit finding: Collection for an elapsed or canceled agreement could be wrong due to temporal calculation inconsistencies between IndexingAgreement and RecurringCollector layers. * Replace isCollectable() with getCollectionInfo() that returns both collectability and duration * Make RecurringCollector the single source of truth for temporal logic * Update IndexingAgreement to call getCollectionInfo() once and pass duration to _tokensToCollect()
Fixes signature replay attack vulnerability where old signed RecurringCollectionAgreementUpdate messages could be replayed to revert agreements to previous terms. ## Changes - Add `nonce` field to RecurringCollectionAgreementUpdate struct (uint32) - Add `updateNonce` field to AgreementData struct to track current nonce - Add nonce validation in RecurringCollector.update() to ensure sequential updates - Update EIP712_RCAU_TYPEHASH to include nonce field - Add comprehensive tests for nonce validation and replay attack prevention - Add RecurringCollectorInvalidUpdateNonce error for invalid nonce attempts ## Implementation Details - Nonces start at 0 when agreement is accepted - Each update must use current nonce + 1 - Nonce is incremented after successful update - Uses uint32 for gas optimization (supports 4B+ updates per agreement) - Single source of truth: nonce stored in AgreementData struct
Implements slippage protection mechanism to prevent silent token loss during rate-limited collections in RecurringCollector agreements. The implementation uses type(uint256).max convention to disable slippage checks, providing users full control over acceptable token loss during rate limiting. Resolves audit finding TRST-L-5: "RecurringCollector silently reduces collected tokens without user consent"
Signed-off-by: Tomás Migone <[email protected]>
Signed-off-by: Tomás Migone <[email protected]>
Signed-off-by: Tomás Migone <[email protected]>
Signed-off-by: Tomás Migone <[email protected]>
Signed-off-by: Tomás Migone <[email protected]>
Signed-off-by: Tomás Migone <[email protected]>
Signed-off-by: Tomás Migone <[email protected]>
Signed-off-by: Tomás Migone <[email protected]>
Signed-off-by: Tomás Migone <[email protected]>
Signed-off-by: Tomás Migone <[email protected]>
…0 headroom Drops the auto-generated public getters for MIN_SECONDS_COLLECTION_WINDOW, CONDITION_ELIGIBILITY_CHECK, EIP712_RCA_TYPEHASH, and EIP712_RCAU_TYPEHASH (~50 bytes each) so the contract stays under the EIP-170 24576-byte limit when later additions land. Tests that read these via the public ABI switch to the literal value with a naming comment. Mirrors the dropped constants and EIP-712 typestrings under @graphprotocol/toolshed/core/recurring-collector so off-chain agents constructing offers have a typed source of truth.
…callback opt-in Replaces the live `payer.code.length != 0` callback dispatch in _preCollectCallbacks/_postCollectCallback with a stored condition flag. An offer that sets CONDITION_AGREEMENT_OWNER is only acceptable when the payer declares ERC-165 support for IAgreementOwner; the flag is then read at collection time, so callback dispatch is frozen to acceptance and unaffected by post-acceptance code changes such as EIP-7702 delegation swaps. This closes the gas-estimator griefing vector where an EOA payer could attach delegation between estimation and execution and cause collect() to revert. Consolidates the two interface-support errors into a single RecurringCollectorPayerDoesNotSupportInterface(payer, interfaceId). Renames _requirePayerToSupportEligibilityCheck to _requirePayerInterfaceSupport and folds both ERC-165 checks into it. Test coverage: - offer(NEW) reverts when CONDITION_AGREEMENT_OWNER is set on a payer that does not declare IAgreementOwner via ERC-165. - offer(UPDATE) re-validates ERC-165 support when an RCAU adds the flag to an already-accepted agreement, and reverts if the payer doesn't declare it. - collect skips both beforeCollection and afterCollection when the flag is unset, even with a contract payer — proves dispatch gates on the stored flag, not live payer.code.length. Addresses TRST-L-10.
…g lows for v03
v03 of the Trust Security report withdraws v02 TRST-M-5 (perpetual thaw
griefing via micro deposits) and v02 TRST-L-6 (update offer cleanup
bypassed via planted offer), and renumbers the remaining lows from
L-7..L-11 down to L-6..L-10.
This commit performs the content side and parks each renamed file
under TRST-L-{old}-{new}.md so the next commit can move each file to
its final v03 path with no cascade and no path conflicts:
- delete v02 TRST-M-5.md and v02 TRST-L-6.md
- update the title line of TRST-L-7..L-11 to the new v03 number
- rename TRST-L-{old}.md -> TRST-L-{old}-{new}.md
Each rename here is a self-contained delete+add pair, so git's default
rename detection picks them up at >=96% similarity without -B.
…nal paths
Pure path rename of the five lows parked in the previous commit to
their final TRST-L-{new}.md paths. Each file's content is byte-
identical across the rename, so git's default detection sees all five
at 100% similarity (no -B required, no cascade).
Adds the v03 report PDF and aligns md responses with the auditor's text. - README: add 2nd fix-review commit, point at v03 PDF, refresh status column, drop M-5/L-6 rows (withdrawn) and add R-14 row, plus a footer noting the dropped findings and where their concerns were addressed. - TRST-M-1, M-4, L-6..L-10: status flipped to Fixed (M-1 main finding Acknowledged); v02 local response promoted into the auditor's "Team Response" section as the auditor incorporated it verbatim; new "Mitigation Review" section added with the v03 verdict. - TRST-R-14: new recommendation about avoiding magic numbers (`getAgreementDetails(0)` should use VERSION_CURRENT). No code changes; the contract fixes for these findings already landed in earlier commits.
…GNED (TRST-L-7) The v03 mitigation review on TRST-L-7 asks for it to be clarified that when a payer is represented by a separate signer (Authorizable), the signer — not the payer — must call cancel() with SCOPE_SIGNED for the revocation to take effect against subsequent accept/update. Updated the IAgreementCollector NatSpec on cancel() to spell out the per-scope caller requirement, including that combining SCOPE_SIGNED with SCOPE_PENDING / SCOPE_ACTIVE only makes sense when msg.sender is both payer and signer. Mirrored the clarification in the RecurringCollector implementation comment so the dev-doc and inline comment agree.
… safety margin (TRST-L-8) The v03 mitigation review on TRST-L-8 asks for tests that ensure the defined CALLBACK_GAS_OVERHEAD constant stays sufficient as the code evolves. Existing tests cover the lower bound (precheck reverts under tight gas) but do not assert the upper bound — that when the precheck passes, the EIP-150 cap still forwards at least MAX_PAYER_CALLBACK_GAS to the callee. Added recordedBeforeCollectionGasleft / recordedAfterCollectionGasleft to MockAgreementOwner, sampled at the first opcode of each callback, and a regression test asserting both stay within 500 gas of MAX. The current overhead constant lands the recorded value ~300 below MAX (function dispatch overhead), leaving ~200 gas of headroom against the 500 tolerance — so an edit that adds pre-CALL Solidity overhead trips the alarm before CALLBACK_GAS_OVERHEAD becomes outright insufficient.
…GIBILITY_CHECK (TRST-L-9) The v03 mitigation review on TRST-L-9 asks for it to be documented that turning CONDITION_ELIGIBILITY_CHECK on against an EOA payer is considered fully trusting that payer, since EIP-7702 lets the EOA attach a contract that returns false from isEligible() to block collections at any time. The existing comment treated the flag as a plain opt-in feature without naming the EOA-via-7702 caveat. Expanded the NatSpec on the constant declaration so the trust boundary is visible at the same site readers consult when deciding whether to opt into the flag for a given payer.
…Details (TRST-R-14) The v03 report's TRST-R-14 flags _getAgreementProvider passing 0 as the index argument to IAgreementCollector.getAgreementDetails: that 0 is no longer a placeholder, it now selects VERSION_CURRENT at the collector, and the named constant communicates intent and survives any future remapping of version indices. Imports VERSION_CURRENT from the interface and substitutes it at the single call site.
Indexing payments management audit fixed rebased
🚨 Report Summary
For more details view the full report in OpenZeppelin Code Inspector |
- subgraph-service: ScopedCancelActive integration test leaked the payer prank into post-cancel view-reads via resetPrank (startPrank). When fuzz picked rca.payer == SubgraphService's auto-deployed proxy admin (0x8816d8...), the verification reads on subgraphService reverted with ProxyDeniedAdminAccess. Switched to single-shot vm.prank for the cancel call and added a deterministic regression test using the failing seed. - horizon: TRST-L-8's MAX_PAYER_CALLBACK_GAS margin test asserts the production gas overhead, but `forge coverage` disables the optimizer, legitimately growing the dispatch δ past tolerance. Skip under FOUNDRY_COVERAGE=1 (set by the package's coverage scripts); direct `forge coverage` invocations still fail loudly. - foundry.toml (all 4 packages): exclude block-timestamp lint — pure noise across this codebase.
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
After the audit branch was rebased onto current main as indexing-payments-management-audit-fixed-rebased, the linear chain was re-signed and the SHAs cited throughout the report no longer resolve. Anchor the pre-rebase tip at tag indexing-payments-management-audit-pre-rebase and provide the mapping plus a one-line equivalence check.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1331 +/- ##
==========================================
+ Coverage 88.62% 91.16% +2.54%
==========================================
Files 75 81 +6
Lines 4615 5342 +727
Branches 823 975 +152
==========================================
+ Hits 4090 4870 +780
+ Misses 504 449 -55
- Partials 21 23 +2
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Sign-off commit bbec75e. v04 addressed recommendations only; no new findings or status changes versus v03.
| address graphTokenGateway, | ||
| address graphProxyAdmin, | ||
| address graphCuration | ||
| address graphProxyAdmin |
There was a problem hiding this comment.
this is a breaking change for the event, I don't think it's being used by the network subgraph but maybe we'll want to update the abi eventually so it's not broken.
| * @param allocationId The id of the allocation | ||
| * @param subgraphDeploymentId The id of the subgraph deployment | ||
| */ | ||
| event LegacyAllocationMigrated( |
There was a problem hiding this comment.
I believe this is no longer being used so we could delete. Maybe leave for a post-post-horizon cleanup 😅
| * - getRewards | ||
| * These functions may overestimate the actual rewards due to changes in the total supply | ||
| * until the actual takeRewards function is called. | ||
| * custom:security-contact Please email security+contracts@ thegraph.com (remove space) if you find any bugs. We might have an active bug bounty program. |
There was a problem hiding this comment.
security+contracts@ thegraph.com (remove space)
why can't we have the proper email without the space? Also, shouldn't this be:
@custom:security-contact
?
| * @notice Accept a Recurring Collection Agreement. | ||
| * @dev Caller must be the data service the RCA was issued to. | ||
| * If `signature` is non-empty: checks `rca.deadline >= block.timestamp` and verifies the ECDSA signature. | ||
| * If `signature` is empty: the payer must be a contract implementing {IAgreementOwner.approveAgreement} |
There was a problem hiding this comment.
This function doesn't exist for IAgreementOwner
Rebase of #1301 (
indexing-payments-management-audit) and #1325 (indexing-payments-management-audit-fix-2-light) onto currentmain.Commits being compared
Drift introduced by main since the original merge base
23 files. Zero file-level overlap with the audit-branch surface, no behavioural interaction with anything under audit. Single substantive production-contract change:
This contract change was separately audited and is not in scope of this audit.
For reference, audit scope files (with exclusion added for token-distribution):
Equivalence checks