Skip to content

Commit 6f376cd

Browse files
authored
fix: ensure rewards manager looks at subgraph service and staking for… (#982)
* fix: ensure rewards manager looks at subgraph service and staking for allos Signed-off-by: Tomás Migone <[email protected]> * fix: typo Signed-off-by: Tomás Migone <[email protected]> --------- Signed-off-by: Tomás Migone <[email protected]>
1 parent 1741ea2 commit 6f376cd

File tree

11 files changed

+141
-34
lines changed

11 files changed

+141
-34
lines changed

packages/contracts/contracts/rewards/IRewardsIssuer.sol

+17-3
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ pragma solidity ^0.7.6 || 0.8.26;
55
interface IRewardsIssuer {
66
/**
77
* @dev Get allocation data to calculate rewards issuance
8-
* @param allocationId The allocation ID
8+
* @param allocationId The allocation Id
99
* @return indexer The indexer address
10-
* @return subgraphDeploymentID Subgraph deployment id for the allocation
10+
* @return subgraphDeploymentId Subgraph deployment id for the allocation
1111
* @return tokens Amount of allocated tokens
1212
* @return accRewardsPerAllocatedToken Rewards snapshot
1313
*/
@@ -16,5 +16,19 @@ interface IRewardsIssuer {
1616
)
1717
external
1818
view
19-
returns (address indexer, bytes32 subgraphDeploymentID, uint256 tokens, uint256 accRewardsPerAllocatedToken);
19+
returns (address indexer, bytes32 subgraphDeploymentId, uint256 tokens, uint256 accRewardsPerAllocatedToken);
20+
21+
/**
22+
* @notice Return the total amount of tokens allocated to subgraph.
23+
* @param _subgraphDeploymentId Deployment Id for the subgraph
24+
* @return Total tokens allocated to subgraph
25+
*/
26+
function getSubgraphAllocatedTokens(bytes32 _subgraphDeploymentId) external view returns (uint256);
27+
28+
/**
29+
* @notice Wether or not an allocation is active (i.e open)
30+
* @param _allocationId Allocation Id
31+
* @return Wether or not the allocation is active
32+
*/
33+
function isActiveAllocation(address _allocationId) external view returns (bool);
2034
}

packages/contracts/contracts/rewards/IRewardsManager.sol

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ interface IRewardsManager {
1919

2020
function setMinimumSubgraphSignal(uint256 _minimumSubgraphSignal) external;
2121

22-
function setRewardsIssuer(address _rewardsIssuer, bool _allowed) external;
22+
function setSubgraphService(address _subgraphService) external;
2323

2424
// -- Denylist --
2525

packages/contracts/contracts/rewards/RewardsManager.sol

+46-13
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa
5252
event RewardsDenylistUpdated(bytes32 indexed subgraphDeploymentID, uint256 sinceBlock);
5353

5454
/**
55-
* @dev Emitted when a rewards issuer is updated.
55+
* @dev Emitted when the subgraph service is set
5656
*/
57-
event RewardsIssuerSet(address indexed rewardsIssuer, bool allowed);
57+
event SubgraphServiceSet(address indexed oldSubgraphService, address indexed newSubgraphService);
5858

5959
// -- Modifiers --
6060

@@ -121,9 +121,10 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa
121121
emit ParameterUpdated("minimumSubgraphSignal");
122122
}
123123

124-
function setRewardsIssuer(address _rewardsIssuer, bool _allowed) external override onlyGovernor {
125-
rewardsIssuers[_rewardsIssuer] = _allowed;
126-
emit RewardsIssuerSet(_rewardsIssuer, _allowed);
124+
function setSubgraphService(address _subgraphService) external override onlyGovernor {
125+
address oldSubgraphService = address(subgraphService);
126+
subgraphService = IRewardsIssuer(_subgraphService);
127+
emit SubgraphServiceSet(oldSubgraphService, _subgraphService);
127128
}
128129

129130
// -- Denylist --
@@ -258,7 +259,19 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa
258259
subgraph.accRewardsForSubgraphSnapshot
259260
);
260261

261-
uint256 subgraphAllocatedTokens = staking().getSubgraphAllocatedTokens(_subgraphDeploymentID);
262+
// There are two contributors to subgraph allocated tokens:
263+
// - the legacy allocations on the legacy staking contract
264+
// - the new allocations on the subgraph service
265+
uint256 subgraphAllocatedTokens = 0;
266+
address[2] memory rewardsIssuers = [address(staking()), address(subgraphService)];
267+
for (uint256 i = 0; i < rewardsIssuers.length; i++) {
268+
if (rewardsIssuers[i] != address(0)) {
269+
subgraphAllocatedTokens += IRewardsIssuer(rewardsIssuers[i]).getSubgraphAllocatedTokens(
270+
_subgraphDeploymentID
271+
);
272+
}
273+
}
274+
262275
if (subgraphAllocatedTokens == 0) {
263276
return (0, accRewardsForSubgraph);
264277
}
@@ -321,19 +334,35 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa
321334

322335
/**
323336
* @dev Calculate current rewards for a given allocation on demand.
337+
* The allocation could be a legacy allocation or a new subgraph service allocation.
324338
* @param _allocationID Allocation
325339
* @return Rewards amount for an allocation
326340
*/
327341
function getRewards(address _allocationID) external view override returns (uint256) {
328-
IStaking.AllocationState allocState = staking().getAllocationState(_allocationID);
329-
if (allocState != IStakingBase.AllocationState.Active) {
342+
address rewardsIssuer = address(0);
343+
344+
// Check both the legacy and new allocations
345+
address[2] memory rewardsIssuers = [address(staking()), address(subgraphService)];
346+
for (uint256 i = 0; i < rewardsIssuers.length; i++) {
347+
if (rewardsIssuers[i] != address(0)) {
348+
if (IRewardsIssuer(rewardsIssuers[i]).isActiveAllocation(_allocationID)) {
349+
rewardsIssuer = address(rewardsIssuers[i]);
350+
break;
351+
}
352+
}
353+
}
354+
355+
// Bail if allo does not exist
356+
if (rewardsIssuer == address(0)) {
330357
return 0;
331358
}
332359

333-
IStaking.Allocation memory alloc = staking().getAllocation(_allocationID);
360+
(, bytes32 subgraphDeploymentId, uint256 tokens, uint256 alloAccRewardsPerAllocatedToken) = IRewardsIssuer(
361+
rewardsIssuer
362+
).getAllocationData(_allocationID);
334363

335-
(uint256 accRewardsPerAllocatedToken, ) = getAccRewardsPerAllocatedToken(alloc.subgraphDeploymentID);
336-
return _calcRewards(alloc.tokens, alloc.accRewardsPerAllocatedToken, accRewardsPerAllocatedToken);
364+
(uint256 accRewardsPerAllocatedToken, ) = getAccRewardsPerAllocatedToken(subgraphDeploymentId);
365+
return _calcRewards(tokens, alloAccRewardsPerAllocatedToken, accRewardsPerAllocatedToken);
337366
}
338367

339368
/**
@@ -354,14 +383,18 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa
354383

355384
/**
356385
* @dev Pull rewards from the contract for a particular allocation.
357-
* This function can only be called by the Staking contract.
386+
* This function can only be called by an authorized rewards issuer which are
387+
* the staking contract (for legacy allocations), and the subgraph service (for new allocations).
358388
* This function will mint the necessary tokens to reward based on the inflation calculation.
359389
* @param _allocationID Allocation
360390
* @return Assigned rewards amount
361391
*/
362392
function takeRewards(address _allocationID) external override returns (uint256) {
363393
address rewardsIssuer = msg.sender;
364-
require(rewardsIssuers[rewardsIssuer], "Caller must be a rewards issuer");
394+
require(
395+
rewardsIssuer == address(staking()) || rewardsIssuer == address(subgraphService),
396+
"Caller must be a rewards issuer"
397+
);
365398

366399
(
367400
address indexer,

packages/contracts/contracts/rewards/RewardsManagerStorage.sol

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
// SPDX-License-Identifier: GPL-2.0-or-later
22

3-
pragma solidity ^0.7.6;
3+
pragma solidity ^0.7.6 || 0.8.26;
44

55
import "./IRewardsManager.sol";
66
import "../governance/Managed.sol";
7+
import { IRewardsIssuer } from "./IRewardsIssuer.sol";
78

89
contract RewardsManagerV1Storage is Managed {
910
// -- State --
@@ -38,6 +39,6 @@ contract RewardsManagerV4Storage is RewardsManagerV3Storage {
3839
}
3940

4041
contract RewardsManagerV5Storage is RewardsManagerV4Storage {
41-
// List of addresses that are allowed to issue rewards
42-
mapping(address => bool) public rewardsIssuers;
42+
// Address of the subgraph service
43+
IRewardsIssuer public subgraphService;
4344
}

packages/contracts/contracts/staking/IStakingBase.sol

+14
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,20 @@ interface IStakingBase is IStakingData {
361361
*/
362362
function getAllocation(address _allocationID) external view returns (Allocation memory);
363363

364+
/**
365+
* @dev New function to get the allocation data for the rewards manager
366+
* @dev Note that this is only to make tests pass, as the staking contract with
367+
* this changes will never get deployed. HorizonStaking is taking it's place.
368+
*/
369+
function getAllocationData(address _allocationID) external view returns (address, bytes32, uint256, uint256);
370+
371+
/**
372+
* @dev New function to get the allocation active status for the rewards manager
373+
* @dev Note that this is only to make tests pass, as the staking contract with
374+
* this changes will never get deployed. HorizonStaking is taking it's place.
375+
*/
376+
function isActiveAllocation(address _allocationID) external view returns (bool);
377+
364378
/**
365379
* @notice Return the current state of an allocation
366380
* @param _allocationID Allocation identifier

packages/contracts/contracts/staking/Staking.sol

+21
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,27 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M
472472
return __allocations[_allocationID];
473473
}
474474

475+
/**
476+
* @dev New function to get the allocation data for the rewards manager
477+
* @dev Note that this is only to make tests pass, as the staking contract with
478+
* this changes will never get deployed. HorizonStaking is taking it's place.
479+
*/
480+
function getAllocationData(
481+
address _allocationID
482+
) external view override returns (address, bytes32, uint256, uint256) {
483+
Allocation memory alloc = __allocations[_allocationID];
484+
return (alloc.indexer, alloc.subgraphDeploymentID, alloc.tokens, alloc.accRewardsPerAllocatedToken);
485+
}
486+
487+
/**
488+
* @dev New function to get the allocation active status for the rewards manager
489+
* @dev Note that this is only to make tests pass, as the staking contract with
490+
* this changes will never get deployed. HorizonStaking is taking it's place.
491+
*/
492+
function isActiveAllocation(address _allocationID) external view override returns (bool) {
493+
return _getAllocationState(_allocationID) == AllocationState.Active;
494+
}
495+
475496
/**
476497
* @notice Return the current state of an allocation
477498
* @param _allocationID Allocation identifier

packages/contracts/test/unit/rewards/rewards.test.ts

+4-6
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,6 @@ describe('Rewards', () => {
653653
const event = rewardsManager.interface.parseLog(receipt.logs[1]).args
654654
expect(event.indexer).eq(indexer1.address)
655655
expect(event.allocationID).eq(allocationID1)
656-
expect(event.epoch).eq(await epochManager.currentEpoch())
657656
expect(toRound(event.amount)).eq(toRound(expectedIndexingRewards))
658657

659658
// After state
@@ -692,7 +691,7 @@ describe('Rewards', () => {
692691
const tx = staking.connect(indexer1).closeAllocation(allocationID1, randomHexBytes())
693692
await expect(tx)
694693
.emit(rewardsManager, 'RewardsAssigned')
695-
.withArgs(indexer1.address, allocationID1, await epochManager.currentEpoch(), toBN(0))
694+
.withArgs(indexer1.address, allocationID1, toBN(0))
696695
})
697696

698697
it('does not revert with an underflow if the minimum signal changes, and signal came after allocation', async function () {
@@ -710,7 +709,7 @@ describe('Rewards', () => {
710709
const tx = staking.connect(indexer1).closeAllocation(allocationID1, randomHexBytes())
711710
await expect(tx)
712711
.emit(rewardsManager, 'RewardsAssigned')
713-
.withArgs(indexer1.address, allocationID1, await epochManager.currentEpoch(), toBN(0))
712+
.withArgs(indexer1.address, allocationID1, toBN(0))
714713
})
715714

716715
it('does not revert if signal was already under minimum', async function () {
@@ -727,7 +726,7 @@ describe('Rewards', () => {
727726

728727
await expect(tx)
729728
.emit(rewardsManager, 'RewardsAssigned')
730-
.withArgs(indexer1.address, allocationID1, await epochManager.currentEpoch(), toBN(0))
729+
.withArgs(indexer1.address, allocationID1, toBN(0))
731730
})
732731

733732
it('should distribute rewards on closed allocation and send to destination', async function () {
@@ -761,7 +760,6 @@ describe('Rewards', () => {
761760
const event = rewardsManager.interface.parseLog(receipt.logs[1]).args
762761
expect(event.indexer).eq(indexer1.address)
763762
expect(event.allocationID).eq(allocationID1)
764-
expect(event.epoch).eq(await epochManager.currentEpoch())
765763
expect(toRound(event.amount)).eq(toRound(expectedIndexingRewards))
766764

767765
// After state
@@ -853,7 +851,7 @@ describe('Rewards', () => {
853851
const tx = staking.connect(indexer1).closeAllocation(allocationID1, randomHexBytes())
854852
await expect(tx)
855853
.emit(rewardsManager, 'RewardsDenied')
856-
.withArgs(indexer1.address, allocationID1, await epochManager.currentEpoch())
854+
.withArgs(indexer1.address, allocationID1)
857855
})
858856
})
859857
})

packages/horizon/contracts/interfaces/internal/IHorizonStakingExtension.sol

-7
Original file line numberDiff line numberDiff line change
@@ -160,13 +160,6 @@ interface IHorizonStakingExtension is IRewardsIssuer {
160160
*/
161161
function isAllocation(address allocationID) external view returns (bool);
162162

163-
/**
164-
* @notice Return the total amount of tokens allocated to subgraph.
165-
* @param subgraphDeploymentID Deployment ID for the subgraph
166-
* @return Total tokens allocated to subgraph
167-
*/
168-
function getSubgraphAllocatedTokens(bytes32 subgraphDeploymentID) external view returns (uint256);
169-
170163
/**
171164
* @notice Retrun the time in blocks to unstake
172165
* @return Thawing period in blocks

packages/horizon/contracts/staking/HorizonStakingExtension.sol

+9
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,15 @@ contract HorizonStakingExtension is HorizonStakingBase, IL2StakingBase, IHorizon
267267
return __DEPRECATED_subgraphAllocations[subgraphDeploymentID];
268268
}
269269

270+
/**
271+
* @notice Return true if allocation is active.
272+
* @param allocationID Allocation identifier
273+
* @return True if allocation is active
274+
*/
275+
function isActiveAllocation(address allocationID) external view override returns (bool) {
276+
return _getAllocationState(allocationID) == AllocationState.Active;
277+
}
278+
270279
/**
271280
* @notice Get the total amount of tokens staked by the indexer.
272281
* @param indexer Address of the indexer

packages/subgraph-service/contracts/SubgraphService.sol

+24
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ contract SubgraphService is
3333
{
3434
using PPMMath for uint256;
3535
using Allocation for mapping(address => Allocation.State);
36+
using Allocation for Allocation.State;
3637

3738
/**
3839
* @notice Checks that an indexer is registered
@@ -372,6 +373,29 @@ contract SubgraphService is
372373
);
373374
}
374375

376+
/**
377+
* @notice Return the total amount of tokens allocated to subgraph.
378+
* @dev Implements {IRewardsIssuer.getSubgraphAllocatedTokens}
379+
* @dev To be used by the {RewardsManager}.
380+
* @param subgraphDeploymentId Deployment Id for the subgraph
381+
* @return Total tokens allocated to subgraph
382+
*/
383+
function getSubgraphAllocatedTokens(bytes32 subgraphDeploymentId) external view override returns (uint256) {
384+
return subgraphAllocatedTokens[subgraphDeploymentId];
385+
}
386+
387+
/**
388+
* @notice Check if an allocation is open
389+
* @dev Implements {IRewardsIssuer.isAllocationActive}
390+
* @dev To be used by the {RewardsManager}.
391+
*
392+
* @param allocationId The allocation Id
393+
* @return Wether or not the allocation is active
394+
*/
395+
function isActiveAllocation(address allocationId) external view override returns (bool) {
396+
return allocations[allocationId].isOpen();
397+
}
398+
375399
/**
376400
* @notice See {ISubgraphService.getLegacyAllocation}
377401
*/

packages/subgraph-service/test/mocks/MockRewardsManager.sol

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ contract MockRewardsManager is IRewardsManager {
1212

1313
function setMinimumSubgraphSignal(uint256) external {}
1414

15-
function setRewardsIssuer(address, bool) external {}
15+
function setSubgraphService(address) external {}
1616

1717
// -- Denylist --
1818

0 commit comments

Comments
 (0)