Skip to content

Comments

Pending indexing payments and post-Horizon contract changes#1288

Draft
RembrandtK wants to merge 66 commits intomainfrom
rem-baseline-merge
Draft

Pending indexing payments and post-Horizon contract changes#1288
RembrandtK wants to merge 66 commits intomainfrom
rem-baseline-merge

Conversation

@RembrandtK
Copy link
Contributor

@RembrandtK RembrandtK commented Feb 23, 2026

Baseline of pending contract changes that need re-audit after rebasing to main.

For post-Horizon code cleanup to be valid these assumptions about the network must be true:

  1. There are no more legacy allocations open. This can be guaranteed by force closing allocations, which can be done permisionlessly 28 days after Horizon go live.
  2. All service provider legacy unstake operations must be fully thawed. This needs to be monitored but it's also guaranteed as long as the transition period is longer than 28 days.

(Pending merge of #1291 to remove a previous assumption.)

This PR is likely to be split into two, and presented for audit processing in the following order (commits indicative but might need some adjusting):

  1. Indexing payments (81ac02f)
  2. Escrow management #1290
  3. Post-Horizon code cleanup (remaining commits on this PR combined with feat: add back legacy allocation id collision check #1291).

matiasedgeandnode and others added 30 commits June 11, 2025 14:08
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 <tomas@edgeandnode.com>
Signed-off-by: Tomás Migone <tomas@edgeandnode.com>
Signed-off-by: Tomás Migone <tomas@edgeandnode.com>
Signed-off-by: Tomás Migone <tomas@edgeandnode.com>
Signed-off-by: Tomás Migone <tomas@edgeandnode.com>
Signed-off-by: Tomás Migone <tomas@edgeandnode.com>
Signed-off-by: Tomás Migone <tomas@edgeandnode.com>
Signed-off-by: Tomás Migone <tomas@edgeandnode.com>
Signed-off-by: Tomás Migone <tomas@edgeandnode.com>
Signed-off-by: Tomás Migone <tomas@edgeandnode.com>
Maikol and others added 20 commits February 9, 2026 19:42
Prevents stack-too-deep compiler error when running coverage
without optimizer, caused by RecurringCollector contract complexity.
Add forge-lint disable comments for RCA/POI acronym casing in
function and variable names. Remove unused Allocation and
IDataServiceFees imports. Replace unsafe bytes32("poi") cast
with keccak256("poi").
…service coverage

- Revert strict inequality conversions (> x-1, < x+1) back to (>=, <=)
  and suppress gas-strict-inequalities with solhint comments instead
- Suppress gas-small-strings, function-max-lines solhint warnings
- Add --ir-minimum to subgraph-service forge coverage to fix stack too deep
- Change getCollectionInfo param from memory to calldata
- Add @author natspec tag
…-merge

Merges 5 new audit-fix commits from origin/issuance-audit:
- TRST-R-1: reclaim pending rewards on stale allocation resize
- TRST-R-2: improve reward documentation accuracy
- TRST-R-3: remove redundant subgraphAllocatedTokens check
- Rename NO_ALLOCATION to NO_ALLOCATED_TOKENS
- Document minimumSubgraphSignal retroactive application

Conflict resolution:
- AllocationManager.sol: kept thin wrapper, passed maxPOIStaleness
  to AllocationHandler.resizeAllocation()
- AllocationHandler.sol: ported stale reclaim logic (isStale check,
  reclaimRewards, clearPendingRewards) into resizeAllocation()
- resize.t.sol: kept AllocationHandler import, added IAllocation import
  for new test assertions

Auto-merged (verified):
- MockRewardsManager.sol: realistic calcRewards/reclaimRewards
- SubgraphService.t.sol: pending rewards assertion fix
Solhint already has func-name-mixedcase and var-name-mixedcase turned
off. Align forge-lint by excluding these rules in horizon and
subgraph-service to avoid redundant inline suppressions.
Cover pause/unpause access control, setPauseGuardian, and
no-change revert cases.
Remove LibFixedMath, Denominations, DataServiceRescuable and its
interface. None are imported by any source code in horizon or
subgraph-service.
MathUtils only provided min() to horizon/subgraph-service code.
The other two functions were unused. Switch to OZ's Math.min which
is already a dependency, and delete the custom library.
Prevents forge lint from scanning into test/node_modules or other
directories with incompatible dependencies or symlink loops.
- Add contract-level natspec to StakeClaims, IndexingAgreement,
  IndexingAgreementDecoder, IndexingAgreementDecoderRaw
- Suppress gas-indexed-events on StakeClaims events and asm-keccak256 note
- Move forge-lint comments before natspec blocks to fix natspec detection
- Use block-style solhint-disable for function-max-lines suppression
- Change struct params to calldata in AllocationHandler and IndexingAgreement
- Remove orphaned natspec and unnecessary forge-lint comments
- Reorder AllocateParams struct to pack _delegationRatio (uint32) with
  _indexer (address) into a single 32-byte slot
- Add forge-lint unsafe-typecast annotations in RecurringCollector with
  safety rationale for uint32/uint64 casts bounded by require checks
@openzeppelin-code
Copy link

openzeppelin-code bot commented Feb 23, 2026

Pending indexing payments and post-Horizon contract changes

Generated at commit: eb839002ad8448b16300284e49263f201dd09851

🚨 Report Summary

Severity Level Results
Contracts Critical
High
Medium
Low
Note
Total
3
5
0
14
37
59
Dependencies Critical
High
Medium
Low
Note
Total
0
0
0
0
0
0

For more details view the full report in OpenZeppelin Code Inspector

@codecov
Copy link

codecov bot commented Feb 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.80%. Comparing base (ada3155) to head (eb83900).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1288      +/-   ##
==========================================
- Coverage   86.95%   86.80%   -0.16%     
==========================================
  Files          46       46              
  Lines        2492     2485       -7     
  Branches      746      743       -3     
==========================================
- Hits         2167     2157      -10     
- Misses        325      328       +3     
Flag Coverage Δ
unittests 86.80% <100.00%> (-0.16%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

mapping(address indexer => address destination) public override paymentsDestination;

/// @notice The cut data service takes from indexing fee payments. In PPM.
uint256 public indexingFeesCut;
Copy link
Member

Choose a reason for hiding this comment

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

This should be in SubgraphServiceV2Storage no?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, I thought about doing that but since we wanted minimum changes and the audit was done without SubgraphServiceV2Storage I left it unchanged.

@tmigone
Copy link
Member

tmigone commented Feb 23, 2026

Post-horizon clean up additions taken from #1261 and #1271 look good.

Worth keeping this in context for this upgrade:

In order for these changes to be valid there are some assumptions about the network state that must hold:

  1. There are no more legacy allocations open. This can be guaranteed by force closing allocations, which can be done permisionlessly 28 days after Horizon go live.
  2. Legacy allocation ids have been fully migrated to the SubgraphService. This is a privileged account operation that will be executed once Horizon is live and legacy allocations can no longer be created.
  3. All service provider legacy unstake operations must be fully thawed. This needs to be monitored but it's also guaranteed as long as the transition period is longer than 28 days.

Related to 2), we actually need to revert that change, I'll do it shortly in a separate PR.

Maikol and others added 3 commits February 23, 2026 17:14
Signed-off-by: Tomás Migone <tomas@edgeandnode.com>
Signed-off-by: Tomás Migone <tomas@edgeandnode.com>
@tmigone
Copy link
Member

tmigone commented Feb 23, 2026

#1291 addresses the changes mentioned in my previous comment, it also eliminates one of the assumptions:

In order for these changes to be valid there are some assumptions about the network state that must hold:

  1. There are no more legacy allocations open. This can be guaranteed by force closing allocations, which can be done permisionlessly 28 days after Horizon go live.
  2. All service provider legacy unstake operations must be fully thawed. This needs to be monitored but it's also guaranteed as long as the transition period is longer than 28 days.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants