Skip to content

Commit

Permalink
chore(SubgraphService): allow force closing over allocated allocations
Browse files Browse the repository at this point in the history
  • Loading branch information
Maikol committed Sep 19, 2024
1 parent 4387c6c commit 8b51ee3
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 2 deletions.
12 changes: 12 additions & 0 deletions packages/subgraph-service/contracts/SubgraphService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,18 @@ contract SubgraphService is
_closeAllocation(allocationId);
}

/**
* @notice See {ISubgraphService.closeOverAllocatedAllocation}
*/
function closeOverAllocatedAllocation(address allocationId) external override {
Allocation.State memory allocation = allocations.get(allocationId);
require(
_isAllocationOverAllocated(allocation.indexer, delegationRatio),
SubgraphServiceAllocationNotOverAllocated(allocationId)
);
_closeAllocation(allocationId);
}

/**
* @notice See {ISubgraphService.resizeAllocation}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ interface ISubgraphService is IDataServiceFees {
*/
error SubgraphServiceAllocationIsAltruistic(address allocationId);

/**
* @notice Thrown when trying to force close an allocation that is not over-allocated
* @param allocationId The id of the allocation
*/
error SubgraphServiceAllocationNotOverAllocated(address allocationId);

/**
* @notice Close a stale allocation
* @dev This function can be permissionlessly called when the allocation is stale.
Expand All @@ -138,6 +144,20 @@ interface ISubgraphService is IDataServiceFees {
*/
function closeStaleAllocation(address allocationId) external;

/**
* @notice Close an over-allocated allocation
* @dev This function can be permissionlessly called when the allocation is over-allocated.
* This ensures rewards for other allocations are not diluted by an over-allocated allocation
*
* Requirements:
* - Allocation must exist and be open
* - Allocation must be over-allocated
*
* Emits a {AllocationClosed} event.
* @param allocationId The id of the allocation
*/
function closeOverAllocatedAllocation(address allocationId) external;

/**
* @notice Change the amount of tokens in an allocation
* @dev Requirements:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ abstract contract AllocationManager is EIP712Upgradeable, GraphDirectory, Alloca
);

// Check if the indexer is over-allocated and close the allocation if necessary
if (!allocationProvisionTracker.check(_graphStaking(), allocation.indexer, _delegationRatio)) {
if (_isAllocationOverAllocated(allocation.indexer, _delegationRatio)) {
_closeAllocation(_allocationId);
}

Expand Down Expand Up @@ -475,4 +475,14 @@ abstract contract AllocationManager is EIP712Upgradeable, GraphDirectory, Alloca
function _encodeAllocationProof(address _indexer, address _allocationId) internal view returns (bytes32) {
return _hashTypedDataV4(keccak256(abi.encode(EIP712_ALLOCATION_PROOF_TYPEHASH, _indexer, _allocationId)));
}

/**
* @notice Checks if an allocation is over-allocated
* @param _indexer The address of the indexer
* @param _delegationRatio The delegation ratio to consider when locking tokens
* @return True if the allocation is over-allocated, false otherwise
*/
function _isAllocationOverAllocated(address _indexer, uint32 _delegationRatio) internal view returns (bool) {
return !allocationProvisionTracker.check(_graphStaking(), _indexer, _delegationRatio);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,36 @@ contract SubgraphServiceTest is SubgraphServiceSharedTest {
assertEq(subgraphAllocatedTokens, previousSubgraphAllocatedTokens - allocation.tokens);
}

function _closeOverAllocatedAllocation(address _allocationId) internal {
assertTrue(subgraphService.isActiveAllocation(_allocationId));

Allocation.State memory allocation = subgraphService.getAllocation(_allocationId);
uint256 previousSubgraphAllocatedTokens = subgraphService.getSubgraphAllocatedTokens(
allocation.subgraphDeploymentId
);

vm.expectEmit(address(subgraphService));
emit AllocationManager.AllocationClosed(
allocation.indexer,
_allocationId,
allocation.subgraphDeploymentId,
allocation.tokens
);

// close over allocated allocation
subgraphService.closeOverAllocatedAllocation(_allocationId);

// update allocation
allocation = subgraphService.getAllocation(_allocationId);

// check allocation
assertEq(allocation.closedAt, block.timestamp);

// check subgraph deployment allocated tokens
uint256 subgraphAllocatedTokens = subgraphService.getSubgraphAllocatedTokens(subgraphDeployment);
assertEq(subgraphAllocatedTokens, previousSubgraphAllocatedTokens - allocation.tokens);
}

struct IndexingRewardsData {
bytes32 poi;
uint256 tokensIndexerRewards;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;

import "forge-std/Test.sol";

import { IGraphPayments } from "@graphprotocol/horizon/contracts/interfaces/IGraphPayments.sol";
import { ISubgraphService } from "../../../contracts/interfaces/ISubgraphService.sol";
import { SubgraphServiceTest } from "../SubgraphService.t.sol";

contract SubgraphServiceAllocationCloseOverAllocatedTest is SubgraphServiceTest {

address private permissionlessBob = makeAddr("permissionlessBob");

/*
* TESTS
*/

function test_SubgraphService_Allocation_CloseOverAllocated(
uint256 tokens
) public useIndexer useAllocation(tokens) {
// thaw some tokens to become over allocated
staking.thaw(users.indexer, address(subgraphService), tokens / 2);

resetPrank(permissionlessBob);
_closeOverAllocatedAllocation(allocationID);
}

function test_SubgraphService_Allocation_CloseOverAllocated_AfterCollecting(
uint256 tokens
) public useIndexer useAllocation(tokens) {
// Simulate POIs being submitted
uint8 numberOfPOIs = 5;
uint256 timeBetweenPOIs = 5 days;

for (uint8 i = 0; i < numberOfPOIs; i++) {
// Skip forward
skip(timeBetweenPOIs);

bytes memory data = abi.encode(allocationID, bytes32("POI1"));
_collect(users.indexer, IGraphPayments.PaymentTypes.IndexingRewards, data);
}

// thaw some tokens to become over allocated
staking.thaw(users.indexer, address(subgraphService), tokens / 2);

// Close the over allocated allocation
resetPrank(permissionlessBob);
_closeOverAllocatedAllocation(allocationID);
}

function test_SubgraphService_Allocation_CloseOverAllocated_RevertIf_NotOverAllocated(
uint256 tokens
) public useIndexer useAllocation(tokens) {
resetPrank(permissionlessBob);
vm.expectRevert(
abi.encodeWithSelector(
ISubgraphService.SubgraphServiceAllocationNotOverAllocated.selector,
allocationID
)
);
subgraphService.closeOverAllocatedAllocation(allocationID);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ contract SubgraphServiceCollectIndexingTest is SubgraphServiceTest {
}
}

function test_SubgraphService_Collect_Indexing_RevertWhen_OverAllocated(uint256 tokens) public useIndexer {
function test_SubgraphService_Collect_Indexing_OverAllocated(uint256 tokens) public useIndexer {
tokens = bound(tokens, minimumProvisionTokens * 2, 10_000_000_000 ether);

// setup allocation
Expand Down

0 comments on commit 8b51ee3

Please sign in to comment.