Skip to content

Commit 9fdcf37

Browse files
committed
fix: ensure getRewards and takeRewards return 0 for closed allos (OZ L-11)
Signed-off-by: Tomás Migone <[email protected]>
1 parent 34a771c commit 9fdcf37

File tree

9 files changed

+80
-25
lines changed

9 files changed

+80
-25
lines changed

packages/contracts/contracts/rewards/IRewardsIssuer.sol

+3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ pragma solidity ^0.7.6 || 0.8.27;
55
interface IRewardsIssuer {
66
/**
77
* @dev Get allocation data to calculate rewards issuance
8+
*
89
* @param allocationId The allocation Id
10+
* @return isActive Whether the allocation is active or not
911
* @return indexer The indexer address
1012
* @return subgraphDeploymentId Subgraph deployment id for the allocation
1113
* @return tokens Amount of allocated tokens
@@ -18,6 +20,7 @@ interface IRewardsIssuer {
1820
external
1921
view
2022
returns (
23+
bool isActive,
2124
address indexer,
2225
bytes32 subgraphDeploymentId,
2326
uint256 tokens,

packages/contracts/contracts/rewards/RewardsManager.sol

+20-9
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa
316316
/**
317317
* @dev Calculate current rewards for a given allocation on demand.
318318
* The allocation could be a legacy allocation or a new subgraph service allocation.
319+
* Returns 0 if the allocation is not active.
319320
* @param _allocationID Allocation
320321
* @return Rewards amount for an allocation
321322
*/
@@ -326,13 +327,18 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa
326327
);
327328

328329
(
330+
bool isActive,
329331
,
330332
bytes32 subgraphDeploymentId,
331333
uint256 tokens,
332334
uint256 alloAccRewardsPerAllocatedToken,
333335
uint256 accRewardsPending
334336
) = IRewardsIssuer(_rewardsIssuer).getAllocationData(_allocationID);
335337

338+
if (!isActive) {
339+
return 0;
340+
}
341+
336342
(uint256 accRewardsPerAllocatedToken, ) = getAccRewardsPerAllocatedToken(subgraphDeploymentId);
337343
return
338344
accRewardsPending.add(_calcRewards(tokens, alloAccRewardsPerAllocatedToken, accRewardsPerAllocatedToken));
@@ -372,6 +378,7 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa
372378
* This function can only be called by an authorized rewards issuer which are
373379
* the staking contract (for legacy allocations), and the subgraph service (for new allocations).
374380
* This function will mint the necessary tokens to reward based on the inflation calculation.
381+
* Mints 0 tokens if the allocation is not active.
375382
* @param _allocationID Allocation
376383
* @return Assigned rewards amount
377384
*/
@@ -383,6 +390,7 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa
383390
);
384391

385392
(
393+
bool isActive,
386394
address indexer,
387395
bytes32 subgraphDeploymentID,
388396
uint256 tokens,
@@ -398,15 +406,18 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa
398406
return 0;
399407
}
400408

401-
// Calculate rewards accrued by this allocation
402-
uint256 rewards = accRewardsPending.add(
403-
_calcRewards(tokens, accRewardsPerAllocatedToken, updatedAccRewardsPerAllocatedToken)
404-
);
405-
if (rewards > 0) {
406-
// Mint directly to rewards issuer for the reward amount
407-
// The rewards issuer contract will do bookkeeping of the reward and
408-
// assign in proportion to each stakeholder incentive
409-
graphToken().mint(rewardsIssuer, rewards);
409+
uint256 rewards = 0;
410+
if (isActive) {
411+
// Calculate rewards accrued by this allocation
412+
rewards = accRewardsPending.add(
413+
_calcRewards(tokens, accRewardsPerAllocatedToken, updatedAccRewardsPerAllocatedToken)
414+
);
415+
if (rewards > 0) {
416+
// Mint directly to rewards issuer for the reward amount
417+
// The rewards issuer contract will do bookkeeping of the reward and
418+
// assign in proportion to each stakeholder incentive
419+
graphToken().mint(rewardsIssuer, rewards);
420+
}
410421
}
411422

412423
emit HorizonRewardsAssigned(indexer, _allocationID, rewards);

packages/contracts/contracts/staking/IStakingBase.sol

+1-1
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ interface IStakingBase is IStakingData {
368368
*/
369369
function getAllocationData(
370370
address _allocationID
371-
) external view returns (address, bytes32, uint256, uint256, uint256);
371+
) external view returns (bool, address, bytes32, uint256, uint256, uint256);
372372

373373
/**
374374
* @dev New function to get the allocation active status for the rewards manager

packages/contracts/contracts/staking/Staking.sol

+14-4
Original file line numberDiff line numberDiff line change
@@ -479,9 +479,18 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M
479479
*/
480480
function getAllocationData(
481481
address _allocationID
482-
) external view override returns (address, bytes32, uint256, uint256, uint256) {
482+
) external view override returns (bool, address, bytes32, uint256, uint256, uint256) {
483483
Allocation memory alloc = __allocations[_allocationID];
484-
return (alloc.indexer, alloc.subgraphDeploymentID, alloc.tokens, alloc.accRewardsPerAllocatedToken, 0);
484+
bool isActive = _getAllocationState(_allocationID) == AllocationState.Active;
485+
486+
return (
487+
isActive,
488+
alloc.indexer,
489+
alloc.subgraphDeploymentID,
490+
alloc.tokens,
491+
alloc.accRewardsPerAllocatedToken,
492+
0
493+
);
485494
}
486495

487496
/**
@@ -810,8 +819,6 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M
810819
require(isIndexerOrOperator, "!auth");
811820
}
812821

813-
// Close the allocation
814-
__allocations[_allocationID].closedAtEpoch = alloc.closedAtEpoch;
815822

816823
// -- Rewards Distribution --
817824

@@ -836,6 +843,9 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M
836843
);
837844
}
838845

846+
// Close the allocation
847+
__allocations[_allocationID].closedAtEpoch = alloc.closedAtEpoch;
848+
839849
emit AllocationClosed(
840850
alloc.indexer,
841851
alloc.subgraphDeploymentID,

packages/contracts/scripts/build

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ yarn compile
1010
tsc
1111

1212
# Copy types and abis to distribution folder
13-
cp -R build/types/* dist/build/types
14-
cp -R build/abis/ dist/abis
13+
cp -R build/types/* dist/contracts/build/types
14+
cp -R build/abis/ dist/contracts/build/abis
1515

1616
# Move compiled types ts
17-
mv dist/build/types dist/types
17+
mv dist/contracts/build/types dist/types

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

+25
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
toGRT,
2222
} from '@graphprotocol/sdk'
2323
import type { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
24+
import { impersonateAccount, setBalance } from '../../../../sdk/src/helpers'
2425

2526
const MAX_PPM = 1000000
2627

@@ -838,6 +839,30 @@ describe('Rewards', () => {
838839
.emit(rewardsManager, 'RewardsDenied')
839840
.withArgs(indexer1.address, allocationID1)
840841
})
842+
843+
it('should not distribute rewards if allocation is not active', async function () {
844+
// Setup
845+
await setupIndexerAllocation()
846+
847+
// Jump and close allocation
848+
await helpers.mineEpoch(epochManager)
849+
await staking.connect(indexer1).closeAllocation(allocationID1, randomHexBytes())
850+
851+
// Jump some more
852+
await helpers.mineEpoch(epochManager, 10)
853+
854+
// Impersonate staking contract
855+
const impersonatedStaking = await impersonateAccount(staking.address)
856+
await setBalance(staking.address, toGRT('1000000'))
857+
858+
// Distribute rewards
859+
const tx = await rewardsManager.connect(impersonatedStaking).takeRewards(allocationID1)
860+
const receipt = await tx.wait()
861+
const event = rewardsManager.interface.parseLog(receipt.logs[0]).args
862+
expect(event.indexer).eq(indexer1.address)
863+
expect(event.allocationID).eq(allocationID1)
864+
expect(toRound(event.amount)).eq(toRound(BigNumber.from(0)))
865+
})
841866
})
842867
})
843868

packages/horizon/contracts/staking/HorizonStakingExtension.sol

+6-5
Original file line numberDiff line numberDiff line change
@@ -223,9 +223,10 @@ contract HorizonStakingExtension is HorizonStakingBase, IHorizonStakingExtension
223223
/// @inheritdoc IRewardsIssuer
224224
function getAllocationData(
225225
address allocationID
226-
) external view override returns (address, bytes32, uint256, uint256, uint256) {
226+
) external view override returns (bool, address, bytes32, uint256, uint256, uint256) {
227227
Allocation memory allo = __DEPRECATED_allocations[allocationID];
228-
return (allo.indexer, allo.subgraphDeploymentID, allo.tokens, allo.accRewardsPerAllocatedToken, 0);
228+
bool isActive = _getAllocationState(allocationID) == AllocationState.Active;
229+
return (isActive, allo.indexer, allo.subgraphDeploymentID, allo.tokens, allo.accRewardsPerAllocatedToken, 0);
229230
}
230231

231232
/// @inheritdoc IHorizonStakingExtension
@@ -351,9 +352,6 @@ contract HorizonStakingExtension is HorizonStakingBase, IHorizonStakingExtension
351352
require(isIndexerOrOperator, "!auth");
352353
}
353354

354-
// Close the allocation
355-
__DEPRECATED_allocations[_allocationID].closedAtEpoch = alloc.closedAtEpoch;
356-
357355
// -- Rewards Distribution --
358356

359357
// Process non-zero-allocation rewards tracking
@@ -377,6 +375,9 @@ contract HorizonStakingExtension is HorizonStakingBase, IHorizonStakingExtension
377375
alloc.tokens;
378376
}
379377

378+
// Close the allocation
379+
__DEPRECATED_allocations[_allocationID].closedAtEpoch = alloc.closedAtEpoch;
380+
380381
emit AllocationClosed(
381382
alloc.indexer,
382383
alloc.subgraphDeploymentID,

packages/subgraph-service/contracts/SubgraphService.sol

+2-1
Original file line numberDiff line numberDiff line change
@@ -382,9 +382,10 @@ contract SubgraphService is
382382
/// @inheritdoc IRewardsIssuer
383383
function getAllocationData(
384384
address allocationId
385-
) external view override returns (address, bytes32, uint256, uint256, uint256) {
385+
) external view override returns (bool, address, bytes32, uint256, uint256, uint256) {
386386
Allocation.State memory allo = _allocations[allocationId];
387387
return (
388+
allo.isOpen(),
388389
allo.indexer,
389390
allo.subgraphDeploymentId,
390391
allo.tokens,

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

+6-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ interface IRewardsIssuer {
1414
)
1515
external
1616
view
17-
returns (address indexer, bytes32 subgraphDeploymentId, uint256 tokens, uint256 accRewardsPerAllocatedToken);
17+
returns (bool isActive, address indexer, bytes32 subgraphDeploymentId, uint256 tokens, uint256 accRewardsPerAllocatedToken);
1818
}
1919

2020
contract MockRewardsManager is IRewardsManager {
@@ -71,10 +71,14 @@ contract MockRewardsManager is IRewardsManager {
7171

7272
function takeRewards(address _allocationID) external returns (uint256) {
7373
address rewardsIssuer = msg.sender;
74-
(, , uint256 tokens, uint256 accRewardsPerAllocatedToken) = IRewardsIssuer(rewardsIssuer).getAllocationData(
74+
(bool isActive, , , uint256 tokens, uint256 accRewardsPerAllocatedToken) = IRewardsIssuer(rewardsIssuer).getAllocationData(
7575
_allocationID
7676
);
7777

78+
if (!isActive) {
79+
return 0;
80+
}
81+
7882
uint256 accRewardsPerTokens = tokens.mulPPM(rewardsPerSignal);
7983
uint256 rewards = accRewardsPerTokens - accRewardsPerAllocatedToken;
8084
token.mint(rewardsIssuer, rewards);

0 commit comments

Comments
 (0)