diff --git a/packages/hardhat-graph-protocol/src/sdk/deployments/horizon/address-book.ts b/packages/hardhat-graph-protocol/src/sdk/deployments/horizon/address-book.ts index 4511757da..e940a6313 100644 --- a/packages/hardhat-graph-protocol/src/sdk/deployments/horizon/address-book.ts +++ b/packages/hardhat-graph-protocol/src/sdk/deployments/horizon/address-book.ts @@ -3,6 +3,9 @@ import { logDebug, logError } from '../../../logger' import { Provider, Signer } from 'ethers' import { AddressBook } from '../../address-book' import { assertObject } from '../../utils/assertion' +import { Contract } from 'ethers' +import { loadArtifact } from '../../lib/artifact' +import { mergeABIs } from '../../utils/abi' import type { GraphHorizonContractName, GraphHorizonContracts } from './contracts' @@ -23,6 +26,20 @@ export class GraphHorizonAddressBook extends AddressBook v.name === item.name) === undefined) { + abi1.push(item) + } + } + return abi1 +} \ No newline at end of file diff --git a/packages/subgraph-service/contracts/SubgraphService.sol b/packages/subgraph-service/contracts/SubgraphService.sol index a4c6596e8..69f6fb6df 100644 --- a/packages/subgraph-service/contracts/SubgraphService.sol +++ b/packages/subgraph-service/contracts/SubgraphService.sol @@ -313,7 +313,7 @@ contract SubgraphService is /** * @notice See {ISubgraphService.closeStaleAllocation} */ - function closeStaleAllocation(address allocationId) external override { + function closeStaleAllocation(address allocationId) external override whenNotPaused { Allocation.State memory allocation = allocations.get(allocationId); require(allocation.isStale(maxPOIStaleness), SubgraphServiceCannotForceCloseAllocation(allocationId)); require(!allocation.isAltruistic(), SubgraphServiceAllocationIsAltruistic(allocationId)); diff --git a/packages/subgraph-service/test/SubgraphBaseTest.t.sol b/packages/subgraph-service/test/SubgraphBaseTest.t.sol index 663442e1f..878d341b0 100644 --- a/packages/subgraph-service/test/SubgraphBaseTest.t.sol +++ b/packages/subgraph-service/test/SubgraphBaseTest.t.sol @@ -75,7 +75,8 @@ abstract contract SubgraphBaseTest is Utils, Constants { delegator: createUser("delegator"), arbitrator: createUser("arbitrator"), fisherman: createUser("fisherman"), - rewardsDestination: createUser("rewardsDestination") + rewardsDestination: createUser("rewardsDestination"), + pauseGuardian: createUser("pauseGuardian") }); deployProtocolContracts(); @@ -191,6 +192,7 @@ abstract contract SubgraphBaseTest is Utils, Constants { epochManager.setEpochLength(EPOCH_LENGTH); subgraphService.setMaxPOIStaleness(maxPOIStaleness); subgraphService.setCurationCut(curationCut); + subgraphService.setPauseGuardian(users.pauseGuardian, true); } function unpauseProtocol() private { diff --git a/packages/subgraph-service/test/shared/HorizonStakingShared.t.sol b/packages/subgraph-service/test/shared/HorizonStakingShared.t.sol index abe425793..66915d043 100644 --- a/packages/subgraph-service/test/shared/HorizonStakingShared.t.sol +++ b/packages/subgraph-service/test/shared/HorizonStakingShared.t.sol @@ -33,6 +33,10 @@ abstract contract HorizonStakingSharedTest is SubgraphBaseTest { staking.delegate(_indexer, _verifier, _tokens, _minSharesOut); } + function _undelegate(address _indexer, address _verifier, uint256 _shares) internal { + staking.undelegate(_indexer, _verifier, _shares); + } + function _setDelegationFeeCut( address _indexer, address _verifier, diff --git a/packages/subgraph-service/test/shared/SubgraphServiceShared.t.sol b/packages/subgraph-service/test/shared/SubgraphServiceShared.t.sol index e3258a09e..fa9d420fb 100644 --- a/packages/subgraph-service/test/shared/SubgraphServiceShared.t.sol +++ b/packages/subgraph-service/test/shared/SubgraphServiceShared.t.sol @@ -31,7 +31,7 @@ abstract contract SubgraphServiceSharedTest is HorizonStakingSharedTest { } modifier useAllocation(uint256 tokens) { - vm.assume(tokens > minimumProvisionTokens); + vm.assume(tokens >= minimumProvisionTokens); vm.assume(tokens < 10_000_000_000 ether); _createProvision(users.indexer, tokens, maxSlashingPercentage, disputePeriod); _register(users.indexer, abi.encode("url", "geoHash", address(0))); diff --git a/packages/subgraph-service/test/subgraphService/allocation/forceClose.t.sol b/packages/subgraph-service/test/subgraphService/allocation/forceClose.t.sol index 5b35f8d4e..1ac1383ab 100644 --- a/packages/subgraph-service/test/subgraphService/allocation/forceClose.t.sol +++ b/packages/subgraph-service/test/subgraphService/allocation/forceClose.t.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.27; import "forge-std/Test.sol"; +import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; import { IGraphPayments } from "@graphprotocol/horizon/contracts/interfaces/IGraphPayments.sol"; import { Allocation } from "../../../contracts/libraries/Allocation.sol"; @@ -91,4 +92,13 @@ contract SubgraphServiceAllocationForceCloseTest is SubgraphServiceTest { ); subgraphService.closeStaleAllocation(allocationID); } + + function test_SubgraphService_Allocation_ForceClose_RevertIf_Paused() public useIndexer useAllocation(1000 ether) { + resetPrank(users.pauseGuardian); + subgraphService.pause(); + + resetPrank(permissionlessBob); + vm.expectRevert(abi.encodeWithSelector(PausableUpgradeable.EnforcedPause.selector)); + subgraphService.closeStaleAllocation(allocationID); + } } diff --git a/packages/subgraph-service/test/subgraphService/allocation/overDelegated.t.sol b/packages/subgraph-service/test/subgraphService/allocation/overDelegated.t.sol new file mode 100644 index 000000000..c9e58355b --- /dev/null +++ b/packages/subgraph-service/test/subgraphService/allocation/overDelegated.t.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import "forge-std/Test.sol"; + +import { IGraphPayments } from "@graphprotocol/horizon/contracts/interfaces/IGraphPayments.sol"; +import { IHorizonStakingTypes } from "@graphprotocol/horizon/contracts/interfaces/internal/IHorizonStakingTypes.sol"; + +import { Allocation } from "../../../contracts/libraries/Allocation.sol"; +import { ISubgraphService } from "../../../contracts/interfaces/ISubgraphService.sol"; +import { SubgraphServiceTest } from "../SubgraphService.t.sol"; + +contract SubgraphServiceAllocationOverDelegatedTest is SubgraphServiceTest { + + /* + * TESTS + */ + + function test_SubgraphService_Allocation_OverDelegated_NotOverAllocatedAfterUndelegation( + uint256 delegationTokens, + uint256 undelegationTokens + ) public useIndexer { + // Use minimum provision tokens + uint256 indexerTokens = minimumProvisionTokens; + uint256 allocationTokens = indexerTokens * delegationRatio; + // Bound delegation tokens to be over delegated + delegationTokens = bound(delegationTokens, allocationTokens, MAX_TOKENS); + // Assume undelegation tokens to still leave indexer over delegated + vm.assume(undelegationTokens > 1); + vm.assume(undelegationTokens < delegationTokens - allocationTokens); + + // Create provision + token.approve(address(staking), indexerTokens); + _createProvision(users.indexer, indexerTokens, maxSlashingPercentage, disputePeriod); + _register(users.indexer, abi.encode("url", "geoHash", address(0))); + + // Delegate so that indexer is over allocated + resetPrank(users.delegator); + token.approve(address(staking), delegationTokens); + _delegate(users.indexer, address(subgraphService), delegationTokens, 0); + + // Create allocation + resetPrank(users.indexer); + bytes memory data = _createSubgraphAllocationData( + users.indexer, + subgraphDeployment, + allocationIDPrivateKey, + allocationTokens + ); + _startService(users.indexer, data); + + // Undelegate + resetPrank(users.delegator); + _undelegate(users.indexer, address(subgraphService), undelegationTokens); + + // Check that indexer is not over allocated + assertFalse(subgraphService.isOverAllocated(users.indexer)); + } +} diff --git a/packages/subgraph-service/test/utils/Users.sol b/packages/subgraph-service/test/utils/Users.sol index 69c3e411e..0d11fae9e 100644 --- a/packages/subgraph-service/test/utils/Users.sol +++ b/packages/subgraph-service/test/utils/Users.sol @@ -12,4 +12,5 @@ struct Users { address arbitrator; address fisherman; address rewardsDestination; + address pauseGuardian; } \ No newline at end of file