Skip to content

fix: prevent underflow when subgraphs go under the minimum signal threshold #640

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion contracts/rewards/RewardsManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pragma abicoder v2;
import "@openzeppelin/contracts/math/SafeMath.sol";

import "../upgrades/GraphUpgradeable.sol";
import "../staking/libs/MathUtils.sol";

import "./RewardsManagerStorage.sol";
import "./IRewardsManager.sol";
Expand Down Expand Up @@ -281,7 +282,8 @@ contract RewardsManager is RewardsManagerV3Storage, GraphUpgradeable, IRewardsMa
Subgraph storage subgraph = subgraphs[_subgraphDeploymentID];

uint256 accRewardsForSubgraph = getAccRewardsForSubgraph(_subgraphDeploymentID);
uint256 newRewardsForSubgraph = accRewardsForSubgraph.sub(
uint256 newRewardsForSubgraph = MathUtils.diffOrZero(
accRewardsForSubgraph,
subgraph.accRewardsForSubgraphSnapshot
);

Expand Down
76 changes: 76 additions & 0 deletions test/rewards/rewards.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,29 @@ describe('Rewards', () => {
)
}

async function setupIndexerAllocationSignalingAfter() {
// Setup
await epochManager.setEpochLength(10)

// Allocate
const tokensToAllocate = toGRT('12500')
await staking.connect(indexer1.signer).stake(tokensToAllocate)
await staking
.connect(indexer1.signer)
.allocateFrom(
indexer1.address,
subgraphDeploymentID1,
tokensToAllocate,
allocationID1,
metadata,
await channelKey1.generateProof(indexer1.address),
)

// Update total signalled
const signalled1 = toGRT('1500')
await curation.connect(curator1.signer).mint(subgraphDeploymentID1, signalled1, 0)
}

async function setupIndexerAllocationWithDelegation(
tokensToDelegate: BigNumber,
delegationParams: DelegationParameters,
Expand Down Expand Up @@ -636,6 +659,59 @@ describe('Rewards', () => {
expect(toRound(afterTokenSupply)).eq(toRound(expectedTokenSupply))
})

it('does not revert with an underflow if the minimum signal changes', async function () {
// Align with the epoch boundary
await advanceToNextEpoch(epochManager)
// Setup
await setupIndexerAllocation()

await rewardsManager.connect(governor.signer).setMinimumSubgraphSignal(toGRT(14000))

// Jump
await advanceToNextEpoch(epochManager)

// Close allocation. At this point rewards should be collected for that indexer
const tx = staking.connect(indexer1.signer).closeAllocation(allocationID1, randomHexBytes())
await expect(tx)
.emit(rewardsManager, 'RewardsAssigned')
.withArgs(indexer1.address, allocationID1, await epochManager.currentEpoch(), toBN(0))
})

it('does not revert with an underflow if the minimum signal changes, and signal came after allocation', async function () {
// Align with the epoch boundary
await advanceToNextEpoch(epochManager)
// Setup
await setupIndexerAllocationSignalingAfter()

await rewardsManager.connect(governor.signer).setMinimumSubgraphSignal(toGRT(14000))

// Jump
await advanceToNextEpoch(epochManager)

// Close allocation. At this point rewards should be collected for that indexer
const tx = staking.connect(indexer1.signer).closeAllocation(allocationID1, randomHexBytes())
await expect(tx)
.emit(rewardsManager, 'RewardsAssigned')
.withArgs(indexer1.address, allocationID1, await epochManager.currentEpoch(), toBN(0))
})

it('does not revert if signal was already under minimum', async function () {
await rewardsManager.connect(governor.signer).setMinimumSubgraphSignal(toGRT(2000))
// Align with the epoch boundary
await advanceToNextEpoch(epochManager)
// Setup
await setupIndexerAllocation()

// Jump
await advanceToNextEpoch(epochManager)
// Close allocation. At this point rewards should be collected for that indexer
const tx = staking.connect(indexer1.signer).closeAllocation(allocationID1, randomHexBytes())

await expect(tx)
.emit(rewardsManager, 'RewardsAssigned')
.withArgs(indexer1.address, allocationID1, await epochManager.currentEpoch(), toBN(0))
})

it('should distribute rewards on closed allocation and send to destination', async function () {
const destinationAddress = randomHexBytes(20)
await staking.connect(indexer1.signer).setRewardsDestination(destinationAddress)
Expand Down