Conversation
…underflow The old code's two hooks wrote to different fields with inverted semantics: - onSubgraphSignalUpdate set accRewardsForSubgraph (A) from storage - onSubgraphAllocationUpdate set accRewardsForSubgraphSnapshot (S) from a view (storage + pending), so S leads and A lags after allocation updates After the proxy upgrade, _updateSubgraphRewards computed A.sub(S).add(P) which underflows on the intermediate A - S when A < S. Rearranging to A.add(P).sub(S) adds pending rewards first, avoiding the intermediate underflow. S <= A + P always holds because P covers T1→now (a superset of the T1→T2 gap S - A). Observed on Arbitrum Sepolia: A < S by ~7,235 GRT for subgraphs whose last pre-upgrade interaction was onSubgraphAllocationUpdate. All reward operations (signal, allocation, claim) reverted permanently.
There was a problem hiding this comment.
Pull request overview
This PR fixes a SafeMath underflow in _updateSubgraphRewards that caused all reward operations to permanently revert for subgraphs on Arbitrum Sepolia whose last pre-upgrade interaction was onSubgraphAllocationUpdate. The old hook wrote accRewardsForSubgraphSnapshot (S) from a view function (storage + pending), leaving accRewardsForSubgraph (A) at its stored value, so A < S after those calls. The new code's A.sub(S).add(P) underflowed on the intermediate A - S. The fix reorders to A.add(P).sub(S), which is always non-negative since P covers a superset of the gap.
Changes:
- Reorder SafeMath operations in
_updateSubgraphRewardsto compute(A + P) - Sinstead of(A - S) + P, avoiding intermediate underflow. - Add comprehensive test file covering inverted storage state via
hardhat_setStorageAt, accounting correctness, normal operation, and a realistic Arbitrum Sepolia scenario.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
packages/contracts/contracts/rewards/RewardsManager.sol |
Core fix: reorders the undistributedRewards subtraction to avoid SafeMath underflow when A < S (pre-upgrade state) |
packages/contracts-test/tests/unit/rewards/rewards-snapshot-inversion.test.ts |
New test suite validating the fix using direct storage manipulation to reproduce the pre-upgrade inverted state |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
packages/contracts-test/tests/unit/rewards/rewards-snapshot-inversion.test.ts
Show resolved
Hide resolved
Fix underflow in _updateSubgraphRewards
🚨 Report Summary
For more details view the full report in OpenZeppelin Code Inspector |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1302 +/- ##
=======================================
Coverage 88.55% 88.55%
=======================================
Files 75 75
Lines 4615 4615
Branches 981 981
=======================================
Hits 4087 4087
Misses 507 507
Partials 21 21
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:
|
fix(rewards): reorder subtraction in _updateSubgraphRewards to avoid underflow
The old code's two hooks wrote to different fields with inverted semantics:
a view (storage + pending), so S leads and A lags after allocation updates
After the proxy upgrade, _updateSubgraphRewards computed A.sub(S).add(P)
which underflows on the intermediate A - S when A < S. Rearranging to
A.add(P).sub(S) adds pending rewards first, avoiding the intermediate
underflow. S <= A + P always holds because P covers T1→now (a superset
of the T1→T2 gap S - A).
Observed on Arbitrum Sepolia: A < S by ~7,235 GRT for subgraphs whose
last pre-upgrade interaction was onSubgraphAllocationUpdate. All reward
operations (signal, allocation, claim) reverted permanently.