diff --git a/packages/horizon/contracts/data-service/GraphDirectory.sol b/packages/horizon/contracts/data-service/GraphDirectory.sol index ffa1c08ad..085ee8a8b 100644 --- a/packages/horizon/contracts/data-service/GraphDirectory.sol +++ b/packages/horizon/contracts/data-service/GraphDirectory.sol @@ -26,36 +26,36 @@ import { ICuration } from "@graphprotocol/contracts/contracts/curation/ICuration abstract contract GraphDirectory { // -- Graph Horizon contracts -- - /// @dev The Graph Token contract address + /// @notice The Graph Token contract address IGraphToken private immutable GRAPH_TOKEN; - /// @dev The Horizon Staking contract address + /// @notice The Horizon Staking contract address IHorizonStaking private immutable GRAPH_STAKING; - /// @dev The Graph Payments contract address + /// @notice The Graph Payments contract address IGraphPayments private immutable GRAPH_PAYMENTS; - /// @dev The Payments Escrow contract address + /// @notice The Payments Escrow contract address IPaymentsEscrow private immutable GRAPH_PAYMENTS_ESCROW; // -- Graph periphery contracts -- - /// @dev The Graph Controller contract address + /// @notice The Graph Controller contract address IController private immutable GRAPH_CONTROLLER; - /// @dev The Epoch Manager contract address + /// @notice The Epoch Manager contract address IEpochManager private immutable GRAPH_EPOCH_MANAGER; - /// @dev The Rewards Manager contract address + /// @notice The Rewards Manager contract address IRewardsManager private immutable GRAPH_REWARDS_MANAGER; - /// @dev The Token Gateway contract address + /// @notice The Token Gateway contract address ITokenGateway private immutable GRAPH_TOKEN_GATEWAY; - /// @dev The Bridge Escrow contract address + /// @notice The Bridge Escrow contract address IBridgeEscrow private immutable GRAPH_BRIDGE_ESCROW; - /// @dev The Graph Proxy Admin contract address + /// @notice The Graph Proxy Admin contract address IGraphProxyAdmin private immutable GRAPH_PROXY_ADMIN; // -- Legacy Graph contracts -- @@ -104,6 +104,7 @@ abstract contract GraphDirectory { * - `controller` cannot be zero address * * Emits a {GraphDirectoryInitialized} event + * * @param controller The address of the Graph Controller contract. */ constructor(address controller) { diff --git a/packages/horizon/contracts/data-service/interfaces/IDataService.sol b/packages/horizon/contracts/data-service/interfaces/IDataService.sol index aa3ef3dfa..f2ffec1a5 100644 --- a/packages/horizon/contracts/data-service/interfaces/IDataService.sol +++ b/packages/horizon/contracts/data-service/interfaces/IDataService.sol @@ -68,8 +68,7 @@ interface IDataService { * @dev Before registering, the service provider must have created a provision in the * Graph Horizon staking contract with parameters that are compatible with the data service. * - * Verifies the provision parameters and marks it as accepted it in the Graph Horizon - * staking contract using {_acceptProvision}. + * Verifies provision parameters and rejects registration in the event they are not valid. * * Emits a {ServiceProviderRegistered} event. * @@ -82,10 +81,9 @@ interface IDataService { function register(address serviceProvider, bytes calldata data) external; /** - * @notice Accepts the provision of a service provider in the {Graph Horizon staking + * @notice Accepts staged parameters in the provision of a service provider in the {Graph Horizon staking * contract}. - * @dev Provides a way for the data service to revalidate and reaccept a provision that - * had a parameter change. Should call {_acceptProvision}. + * @dev Provides a way for the data service to validate and accept provision parameter changes. Call {_acceptProvision}. * * Emits a {ProvisionAccepted} event. * diff --git a/packages/horizon/contracts/data-service/utilities/ProvisionManager.sol b/packages/horizon/contracts/data-service/utilities/ProvisionManager.sol index ff02978fb..520b7a24b 100644 --- a/packages/horizon/contracts/data-service/utilities/ProvisionManager.sol +++ b/packages/horizon/contracts/data-service/utilities/ProvisionManager.sol @@ -96,6 +96,42 @@ abstract contract ProvisionManager is Initializable, GraphDirectory, ProvisionMa _; } + /** + * @notice External getter for the thawing period range + * @return Minimum thawing period allowed + * @return Maximum thawing period allowed + */ + function getThawingPeriodRange() external view returns (uint64, uint64) { + return _getThawingPeriodRange(); + } + + /** + * @notice External getter for the verifier cut range + * @return Minimum verifier cut allowed + * @return Maximum verifier cut allowed + */ + function getVerifierCutRange() external view returns (uint32, uint32) { + return _getVerifierCutRange(); + } + + /** + * @notice External getter for the provision tokens range + * @return Minimum provision tokens allowed + * @return Maximum provision tokens allowed + */ + function getProvisionTokensRange() external view returns (uint256, uint256) { + return _getProvisionTokensRange(); + } + + /** + * @notice External getter for the delegation ratio range + * @return Minimum delegation ratio allowed + * @return Maximum delegation ratio allowed + */ + function getDelegationRatioRange() external view returns (uint32, uint32) { + return _getDelegationRatioRange(); + } + /** * @notice Initializes the contract and any parent contracts. */ diff --git a/packages/horizon/contracts/interfaces/internal/IHorizonStakingMain.sol b/packages/horizon/contracts/interfaces/internal/IHorizonStakingMain.sol index 33a8f29cc..201e1b75a 100644 --- a/packages/horizon/contracts/interfaces/internal/IHorizonStakingMain.sol +++ b/packages/horizon/contracts/interfaces/internal/IHorizonStakingMain.sol @@ -767,6 +767,9 @@ interface IHorizonStakingMain { * the provider has provisioned stake, and up to the amount of tokens they have provisioned. * If the service provider's stake is not enough, the associated delegation pool might be slashed * depending on the value of the global delegation slashing flag. + * + * Part of the slashed tokens are sent to the `verifierDestination` as a reward. + * * @dev Requirements: * - `tokens` must be less than or equal to the amount of tokens provisioned by the service provider. * - `tokensVerifier` must be less than the provision's tokens times the provision's maximum verifier cut. diff --git a/packages/subgraph-service/contracts/DisputeManager.sol b/packages/subgraph-service/contracts/DisputeManager.sol index e85fb54d7..765d08fbc 100644 --- a/packages/subgraph-service/contracts/DisputeManager.sol +++ b/packages/subgraph-service/contracts/DisputeManager.sol @@ -20,8 +20,7 @@ import { AttestationManager } from "./utilities/AttestationManager.sol"; /* * @title DisputeManager - * @notice Provides a way to align the incentives of participants by having slashing as deterrent - * for incorrect behaviour. + * @notice Provides a way to permissionlessly create disputes for incorrect behavior in the Subgraph Service. * * There are two types of disputes that can be created: Query disputes and Indexing disputes. * @@ -55,13 +54,17 @@ contract DisputeManager is // -- Modifiers -- /** - * @dev Check if the caller is the arbitrator. + * @notice Check if the caller is the arbitrator. */ modifier onlyArbitrator() { require(msg.sender == arbitrator, DisputeManagerNotArbitrator()); _; } + /** + * @notice Check if the dispute exists and is pending. + * @param disputeId The dispute Id + */ modifier onlyPendingDispute(bytes32 disputeId) { require(isDisputeCreated(disputeId), DisputeManagerInvalidDispute(disputeId)); require( @@ -71,18 +74,26 @@ contract DisputeManager is _; } + /** + * @notice Check if the caller is the fisherman of the dispute. + * @param disputeId The dispute Id + */ modifier onlyFisherman(bytes32 disputeId) { require(isDisputeCreated(disputeId), DisputeManagerInvalidDispute(disputeId)); require(msg.sender == disputes[disputeId].fisherman, DisputeManagerNotFisherman()); _; } + /** + * @notice Contract constructor + * @param controller Address of the controller + */ constructor(address controller) GraphDirectory(controller) { _disableInitializers(); } /** - * @dev Initialize this contract. + * @notice Initialize this contract. * @param arbitrator Arbitrator role * @param disputePeriod Dispute period in seconds * @param minimumDeposit Minimum deposit required to create a Dispute @@ -107,7 +118,7 @@ contract DisputeManager is } /** - * @dev Create an indexing dispute for the arbitrator to resolve. + * @notice Create an indexing dispute for the arbitrator to resolve. * The disputes are created in reference to an allocationId * This function is called by a challenger that will need to `_deposit` at * least `minimumDeposit` GRT tokens. @@ -123,7 +134,7 @@ contract DisputeManager is } /** - * @dev Create a query dispute for the arbitrator to resolve. + * @notice Create a query dispute for the arbitrator to resolve. * This function is called by a fisherman that will need to `_deposit` at * least `minimumDeposit` GRT tokens. * @param attestationData Attestation bytes submitted by the fisherman @@ -144,7 +155,7 @@ contract DisputeManager is } /** - * @dev Create query disputes for two conflicting attestations. + * @notice Create query disputes for two conflicting attestations. * A conflicting attestation is a proof presented by two different indexers * where for the same request on a subgraph the response is different. * For this type of dispute the submitter is not required to present a deposit @@ -194,11 +205,11 @@ contract DisputeManager is } /** - * @dev The arbitrator accepts a dispute as being valid. + * @notice The arbitrator accepts a dispute as being valid. * This function will revert if the indexer is not slashable, whether because it does not have * any stake available or the slashing percentage is configured to be zero. In those cases * a dispute must be resolved using drawDispute or rejectDispute. - * @notice Accept a dispute with Id `disputeId` + * @dev Accept a dispute with Id `disputeId` * @param disputeId Id of the dispute to be accepted * @param tokensSlash Amount of tokens to slash from the indexer */ @@ -225,8 +236,8 @@ contract DisputeManager is } /** - * @dev The arbitrator draws dispute. - * @notice Ignore a dispute with Id `disputeId` + * @notice The arbitrator draws dispute. + * @dev Ignore a dispute with Id `disputeId` * @param disputeId Id of the dispute to be disregarded */ function drawDispute(bytes32 disputeId) external override onlyArbitrator onlyPendingDispute(disputeId) { @@ -245,9 +256,9 @@ contract DisputeManager is } /** - * @dev Once the dispute period ends, if the disput status remains Pending, + * @notice Once the dispute period ends, if the disput status remains Pending, * the fisherman can cancel the dispute and get back their initial deposit. - * @notice Cancel a dispute with Id `disputeId` + * @dev Cancel a dispute with Id `disputeId` * @param disputeId Id of the dispute to be cancelled */ function cancelDispute(bytes32 disputeId) external override onlyFisherman(disputeId) onlyPendingDispute(disputeId) { @@ -267,8 +278,8 @@ contract DisputeManager is } /** - * @dev Set the arbitrator address. - * @notice Update the arbitrator to `_arbitrator` + * @notice Set the arbitrator address. + * @dev Update the arbitrator to `_arbitrator` * @param arbitrator The address of the arbitration contract or party */ function setArbitrator(address arbitrator) external override onlyOwner { @@ -276,8 +287,8 @@ contract DisputeManager is } /** - * @dev Set the dispute period. - * @notice Update the dispute period to `_disputePeriod` in seconds + * @notice Set the dispute period. + * @dev Update the dispute period to `_disputePeriod` in seconds * @param disputePeriod Dispute period in seconds */ function setDisputePeriod(uint64 disputePeriod) external override onlyOwner { @@ -285,8 +296,8 @@ contract DisputeManager is } /** - * @dev Set the minimum deposit required to create a dispute. - * @notice Update the minimum deposit to `_minimumDeposit` Graph Tokens + * @notice Set the minimum deposit required to create a dispute. + * @dev Update the minimum deposit to `_minimumDeposit` Graph Tokens * @param minimumDeposit The minimum deposit in Graph Tokens */ function setMinimumDeposit(uint256 minimumDeposit) external override onlyOwner { @@ -294,8 +305,8 @@ contract DisputeManager is } /** - * @dev Set the percent reward that the fisherman gets when slashing occurs. - * @notice Update the reward percentage to `_percentage` + * @notice Set the percent reward that the fisherman gets when slashing occurs. + * @dev Update the reward percentage to `_percentage` * @param fishermanRewardCut_ Reward as a percentage of indexer stake */ function setFishermanRewardCut(uint32 fishermanRewardCut_) external override onlyOwner { @@ -303,7 +314,7 @@ contract DisputeManager is } /** - * @dev Set the maximum percentage that can be used for slashing indexers. + * @notice Set the maximum percentage that can be used for slashing indexers. * @param maxSlashingCut_ Max percentage slashing for disputes */ function setMaxSlashingCut(uint32 maxSlashingCut_) external override onlyOwner { @@ -311,8 +322,8 @@ contract DisputeManager is } /** - * @dev Set the subgraph service address. - * @notice Update the subgraph service to `_subgraphService` + * @notice Set the subgraph service address. + * @dev Update the subgraph service to `_subgraphService` * @param subgraphService The address of the subgraph service contract */ function setSubgraphService(address subgraphService) external override onlyOwner { @@ -320,10 +331,10 @@ contract DisputeManager is } /** - * @dev Get the message hash that a indexer used to sign the receipt. + * @notice Get the message hash that a indexer used to sign the receipt. * Encodes a receipt using a domain separator, as described on * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md#specification. - * @notice Return the message hash used to sign the receipt + * @dev Return the message hash used to sign the receipt * @param receipt Receipt returned by indexer and submitted by fisherman * @return Message hash used to sign the receipt */ @@ -332,7 +343,7 @@ contract DisputeManager is } /** - * @dev Get the verifier cut. + * @notice Get the verifier cut. * @return Verifier cut in percentage (ppm) */ function getVerifierCut() external view override returns (uint32) { @@ -340,13 +351,18 @@ contract DisputeManager is } /** - * @dev Get the dispute period. + * @notice Get the dispute period. * @return Dispute period in seconds */ function getDisputePeriod() external view override returns (uint64) { return disputePeriod; } + /** + * @notice Checks if two attestations are conflicting. + * @param attestation1 The first attestation + * @param attestation2 The second attestation + */ function areConflictingAttestations( Attestation.State memory attestation1, Attestation.State memory attestation2 @@ -355,8 +371,8 @@ contract DisputeManager is } /** - * @dev The arbitrator rejects a dispute as being invalid. - * @notice Reject a dispute with Id `disputeId` + * @notice The arbitrator rejects a dispute as being invalid. + * @dev Reject a dispute with Id `disputeId` * @param disputeId Id of the dispute to be rejected */ function rejectDispute(bytes32 disputeId) public override onlyArbitrator onlyPendingDispute(disputeId) { @@ -378,7 +394,7 @@ contract DisputeManager is } /** - * @dev Returns the indexer that signed an attestation. + * @notice Returns the indexer that signed an attestation. * @param attestation Attestation * @return indexer address */ @@ -396,8 +412,8 @@ contract DisputeManager is } /** - * @dev Return whether a dispute exists or not. - * @notice Return if dispute with Id `disputeId` exists + * @notice Return whether a dispute exists or not. + * @dev Return if dispute with Id `disputeId` exists * @param disputeId True if dispute already exists */ function isDisputeCreated(bytes32 disputeId) public view override returns (bool) { @@ -405,7 +421,7 @@ contract DisputeManager is } /** - * @dev Create a query dispute passing the parsed attestation. + * @notice Create a query dispute passing the parsed attestation. * To be used in createQueryDispute() and createQueryDisputeConflict() * to avoid calling parseAttestation() multiple times * `attestationData` is only passed to be emitted @@ -466,7 +482,7 @@ contract DisputeManager is } /** - * @dev Create indexing dispute internal function. + * @notice Create indexing dispute internal function. * @param _fisherman The challenger creating the dispute * @param _deposit Amount of tokens staked as deposit * @param _allocationId Allocation disputed @@ -508,7 +524,7 @@ contract DisputeManager is } /** - * @dev Resolve the conflicting dispute if there is any for the one passed to this function. + * @notice Resolve the conflicting dispute if there is any for the one passed to this function. * @param _dispute Dispute * @return True if resolved */ @@ -523,7 +539,7 @@ contract DisputeManager is } /** - * @dev Cancel the conflicting dispute if there is any for the one passed to this function. + * @notice Cancel the conflicting dispute if there is any for the one passed to this function. * @param _dispute Dispute * @return True if cancelled */ @@ -538,7 +554,7 @@ contract DisputeManager is } /** - * @dev Pull deposit from submitter account. + * @notice Pull deposit from submitter account. * @param _deposit Amount of tokens to deposit */ function _pullSubmitterDeposit(uint256 _deposit) private { @@ -550,7 +566,7 @@ contract DisputeManager is } /** - * @dev Make the subgraph service contract slash the indexer and reward the challenger. + * @notice Make the subgraph service contract slash the indexer and reward the challenger. * Give the challenger a reward equal to the fishermanRewardPercentage of slashed amount * @param _indexer Address of the indexer * @param _tokensSlash Amount of tokens to slash from the indexer @@ -578,8 +594,8 @@ contract DisputeManager is } /** - * @dev Internal: Set the arbitrator address. - * @notice Update the arbitrator to `_arbitrator` + * @notice Internal: Set the arbitrator address. + * @dev Update the arbitrator to `_arbitrator` * @param _arbitrator The address of the arbitration contract or party */ function _setArbitrator(address _arbitrator) private { @@ -589,8 +605,8 @@ contract DisputeManager is } /** - * @dev Internal: Set the dispute period. - * @notice Update the dispute period to `_disputePeriod` in seconds + * @notice Internal: Set the dispute period. + * @dev Update the dispute period to `_disputePeriod` in seconds * @param _disputePeriod Dispute period in seconds */ function _setDisputePeriod(uint64 _disputePeriod) private { @@ -600,8 +616,8 @@ contract DisputeManager is } /** - * @dev Internal: Set the minimum deposit required to create a dispute. - * @notice Update the minimum deposit to `_minimumDeposit` Graph Tokens + * @notice Internal: Set the minimum deposit required to create a dispute. + * @dev Update the minimum deposit to `_minimumDeposit` Graph Tokens * @param _minimumDeposit The minimum deposit in Graph Tokens */ function _setMinimumDeposit(uint256 _minimumDeposit) private { @@ -611,8 +627,8 @@ contract DisputeManager is } /** - * @dev Internal: Set the percent reward that the fisherman gets when slashing occurs. - * @notice Update the reward percentage to `_percentage` + * @notice Internal: Set the percent reward that the fisherman gets when slashing occurs. + * @dev Update the reward percentage to `_percentage` * @param _fishermanRewardCut Reward as a percentage of indexer stake */ function _setFishermanRewardCut(uint32 _fishermanRewardCut) private { @@ -623,7 +639,7 @@ contract DisputeManager is } /** - * @dev Internal: Set the maximum percentage that can be used for slashing indexers. + * @notice Internal: Set the maximum percentage that can be used for slashing indexers. * @param _maxSlashingCut Max percentage slashing for disputes */ function _setMaxSlashingCut(uint32 _maxSlashingCut) private { @@ -634,8 +650,8 @@ contract DisputeManager is } /** - * @dev Internal: Set the subgraph service address. - * @notice Update the subgraph service to `_subgraphService` + * @notice Internal: Set the subgraph service address. + * @dev Update the subgraph service to `_subgraphService` * @param _subgraphService The address of the subgraph service contract */ function _setSubgraphService(address _subgraphService) private { @@ -645,7 +661,7 @@ contract DisputeManager is } /** - * @dev Returns whether the dispute is for a conflicting attestation or not. + * @notice Returns whether the dispute is for a conflicting attestation or not. * @param _dispute Dispute * @return True conflicting attestation dispute */ diff --git a/packages/subgraph-service/contracts/DisputeManagerStorage.sol b/packages/subgraph-service/contracts/DisputeManagerStorage.sol index bf2b368d6..19e217f12 100644 --- a/packages/subgraph-service/contracts/DisputeManagerStorage.sol +++ b/packages/subgraph-service/contracts/DisputeManagerStorage.sol @@ -6,28 +6,24 @@ import { IDisputeManager } from "./interfaces/IDisputeManager.sol"; import { ISubgraphService } from "./interfaces/ISubgraphService.sol"; abstract contract DisputeManagerV1Storage { - // -- State -- - + /// @notice The Subgraph Service contract address ISubgraphService public subgraphService; - // The arbitrator is solely in control of arbitrating disputes + /// @notice The arbitrator is solely in control of arbitrating disputes address public arbitrator; - // dispute period in seconds + /// @notice dispute period in seconds uint64 public disputePeriod; - // Minimum deposit required to create a Dispute + /// @notice Minimum deposit required to create a Dispute uint256 public minimumDeposit; - // Percentage of indexer slashed funds to assign as a reward to fisherman in successful dispute - // Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%) + /// @notice Percentage of indexer slashed funds to assign as a reward to fisherman in successful dispute. In PPM. uint32 public fishermanRewardCut; - // Maximum percentage of indexer stake that can be slashed on a dispute - // Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%) + /// @notice Maximum percentage of indexer stake that can be slashed on a dispute. In PPM. uint32 public maxSlashingCut; - // Disputes created : disputeID => Dispute - // disputeID - check creation functions to see how disputeID is built - mapping(bytes32 disputeID => IDisputeManager.Dispute dispute) public disputes; + /// @notice List of disputes created + mapping(bytes32 disputeId => IDisputeManager.Dispute dispute) public disputes; } diff --git a/packages/subgraph-service/contracts/SubgraphService.sol b/packages/subgraph-service/contracts/SubgraphService.sol index f39beed4c..f6fa952e5 100644 --- a/packages/subgraph-service/contracts/SubgraphService.sol +++ b/packages/subgraph-service/contracts/SubgraphService.sol @@ -10,7 +10,6 @@ import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/O import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import { DataServicePausableUpgradeable } from "@graphprotocol/horizon/contracts/data-service/extensions/DataServicePausableUpgradeable.sol"; import { DataService } from "@graphprotocol/horizon/contracts/data-service/DataService.sol"; -import { DataServiceRescuable } from "@graphprotocol/horizon/contracts/data-service/extensions/DataServiceRescuable.sol"; import { DataServiceFees } from "@graphprotocol/horizon/contracts/data-service/extensions/DataServiceFees.sol"; import { Directory } from "./utilities/Directory.sol"; import { AllocationManager } from "./utilities/AllocationManager.sol"; @@ -20,13 +19,11 @@ import { PPMMath } from "@graphprotocol/horizon/contracts/libraries/PPMMath.sol" import { Allocation } from "./libraries/Allocation.sol"; import { LegacyAllocation } from "./libraries/LegacyAllocation.sol"; -// TODO: contract needs to be upgradeable contract SubgraphService is Initializable, OwnableUpgradeable, DataService, DataServicePausableUpgradeable, - DataServiceRescuable, DataServiceFees, Directory, AllocationManager, @@ -37,13 +34,25 @@ contract SubgraphService is using PPMMath for uint256; using Allocation for mapping(address => Allocation.State); + /** + * @notice Checks that an indexer is registered + * @param indexer The address of the indexer + */ modifier onlyRegisteredIndexer(address indexer) { require(indexers[indexer].registeredAt != 0, SubgraphServiceIndexerNotRegistered(indexer)); _; } /** - * @dev Strict delegation ratio not enforced. + * @notice Checks that a provision is valid + * @dev A valid provision is defined as one that: + * - has at least the minimum amount of tokens requiered by the subgraph service + * - has a thawing period at least equal to {DisputeManager.disputePeriod} + * - has a verifier cut at most equal to {DisputeManager.verifierCut} + * + * Note that no delegation ratio is enforced here. + * + * @param indexer The address of the indexer */ modifier onlyValidProvision(address indexer) override { _checkProvisionTokens(indexer); @@ -51,15 +60,28 @@ contract SubgraphService is _; } + /** + * @notice Constructor for the SubgraphService contract + * @dev DataService and Directory constructors set a bunch of immutable variables + * @param graphController The address of the Graph Controller contract + * @param disputeManager The address of the DisputeManager contract + * @param tapCollector The address of the TAPCollector contract + * @param curation The address of the Curation contract + */ constructor( address graphController, address disputeManager, - address tapVerifier, + address tapCollector, address curation - ) DataService(graphController) Directory(address(this), tapVerifier, disputeManager, curation) { + ) DataService(graphController) Directory(address(this), tapCollector, disputeManager, curation) { _disableInitializers(); } + /** + * @notice See {ISubgraphService.initialize} + * @dev The thawingPeriod and verifierCut ranges are not set here because they are variables + * on the DisputeManager. We use the {ProvisionManager} overrideable getters to get the ranges. + */ function initialize(uint256 minimumProvisionTokens, uint32 maximumDelegationRatio) external override initializer { __Ownable_init(msg.sender); __DataService_init(); @@ -70,6 +92,24 @@ contract SubgraphService is _setDelegationRatioRange(type(uint32).min, maximumDelegationRatio); } + /** + * @notice + * @dev Implements {IDataService.register} + * + * Requirements: + * - The indexer must not be already registered + * - The URL must not be empty + * - The provision must be valid according to the subgraph service rules + * + * Emits a {ServiceProviderRegistered} event + * + * @param indexer The address of the indexer to register + * @param data Encoded registration data: + * - address `url`: The URL of the indexer + * - string `geohash`: The geohash of the indexer + * - address `rewardsDestination`: The address where the indexer wants to receive indexing rewards. + * Use zero address for automatic reprovisioning to the subgraph service. + */ function register( address indexer, bytes calldata data @@ -91,6 +131,19 @@ contract SubgraphService is emit ServiceProviderRegistered(indexer); } + /** + * @notice Accept staged parameters in the provision of a service provider + * @dev Implements {IDataService-acceptProvision} + * + * Requirements: + * - The indexer must be registered + * - Must have previously staged provision parameters, using {IHorizonStaking-setProvisionParameters} + * - The new provision parameters must be valid according to the subgraph service rules + * + * Emits a {ProvisionAccepted} event + * + * @param indexer The address of the indexer to accept the provision for + */ function acceptProvision( address indexer, bytes calldata @@ -100,6 +153,30 @@ contract SubgraphService is emit ProvisionAccepted(indexer); } + /** + * @notice Allocates tokens to subgraph deployment, manifesting the indexer's commitment to index it + * @dev This is the equivalent of the `allocate` function in the legacy Staking contract. + * + * Requirements: + * - The indexer must be registered + * - The provision must be valid according to the subgraph service rules + * - Allocation id cannot be zero + * - Allocation id cannot be reused from the legacy staking contract + * - The indexer must have enough available tokens to allocate + * + * The `allocationProof` is a 65-bytes Ethereum signed message of `keccak256(indexerAddress,allocationId)`. + * + * See {AllocationManager-allocate} for more details. + * + * Emits {ServiceStarted} and {AllocationCreated} events + * + * @param indexer The address of the indexer + * @param data Encoded data: + * - bytes32 `subgraphDeploymentId`: The id of the subgraph deployment + * - uint256 `tokens`: The amount of tokens to allocate + * - address `allocationId`: The id of the allocation + * - bytes `allocationProof`: Signed proof of the allocation id address ownership + */ function startService( address indexer, bytes calldata data @@ -119,6 +196,25 @@ contract SubgraphService is emit ServiceStarted(indexer); } + /** + * @notice Close an allocation, indicating that the indexer has stopped indexing the subgraph deployment + * @dev This is the equivalent of the `closeAllocation` function in the legacy Staking contract. + * There are a few notable differences with the legacy function: + * - allocations are nowlong lived. All service payments, including indexing rewards, should be collected periodically + * without the need of closing the allocation. Allocations should only be closed when indexers want to reclaim the allocated + * tokens for other purposes. + * - No POI is required to close an allocation. Indexers should present POIs to collect indexing rewards using {collect}. + * + * Requirements: + * - The indexer must be registered + * - Allocation must exist and be open + * + * Emits {ServiceStopped} and {AllocationClosed} events + * + * @param indexer The address of the indexer + * @param data Encoded data: + * - address `allocationId`: The id of the allocation + */ function stopService( address indexer, bytes calldata data @@ -128,20 +224,27 @@ contract SubgraphService is emit ServiceStopped(indexer); } - function resizeAllocation( - address indexer, - address allocationId, - uint256 tokens - ) - external - onlyProvisionAuthorized(indexer) - onlyValidProvision(indexer) - onlyRegisteredIndexer(indexer) - whenNotPaused - { - _resizeAllocation(allocationId, tokens, maximumDelegationRatio); - } - + /** + * @notice Collects payment for the service provided by the indexer + * Allows collecting different types of payments such as query fees and indexing rewards. + * It uses Graph Horizon payments protocol to process payments. + * Reverts if the payment type is not supported. + * @dev This function is the equivalent of the `collect` function for query fees and the `closeAllocation` function + * for indexing rewards in the legacy Staking contract. + * + * Requirements: + * - The indexer must be registered + * - The provision must be valid according to the subgraph service rules + * + * Emits a {ServicePaymentCollected} event. Emits payment type specific events. + * + * For query fees, see {SubgraphService-_collectQueryFees} for more details. + * For indexing rewards, see {AllocationManager-_collectIndexingRewards} for more details. + * + * @param indexer The address of the indexer + * @param paymentType The type of payment to collect as defined in {IGraphPayments} + * @param data Encoded data to fulfill the payment. The structure of the data depends on the payment type. See above. + */ function collect( address indexer, IGraphPayments.PaymentTypes paymentType, @@ -160,12 +263,46 @@ contract SubgraphService is emit ServicePaymentCollected(indexer, paymentType, paymentCollected); } + /** + * @notice Slash an indexer + * @dev Slashing is delegated to the {DisputeManager} contract which is the only one that can call this + * function. + * + * See {IHorizonStaking-slash} for more details. + * + * Emits a {ServiceProviderSlashed} event. + * + * @param indexer The address of the indexer to be slashed + * @param data Encoded data: + * - uint256 `tokens`: The amount of tokens to slash + * - uint256 `reward`: The amount of tokens to reward the slasher + */ function slash(address indexer, bytes calldata data) external override onlyDisputeManager whenNotPaused { (uint256 tokens, uint256 reward) = abi.decode(data, (uint256, uint256)); _graphStaking().slash(indexer, tokens, reward, address(_disputeManager())); emit ServiceProviderSlashed(indexer, tokens); } + /** + * @notice See {ISubgraphService.resizeAllocation} + */ + function resizeAllocation( + address indexer, + address allocationId, + uint256 tokens + ) + external + onlyProvisionAuthorized(indexer) + onlyValidProvision(indexer) + onlyRegisteredIndexer(indexer) + whenNotPaused + { + _resizeAllocation(allocationId, tokens, maximumDelegationRatio); + } + + /** + * @notice See {ISubgraphService.migrateLegacyAllocation} + */ function migrateLegacyAllocation( address indexer, address allocationId, @@ -174,14 +311,55 @@ contract SubgraphService is _migrateLegacyAllocation(indexer, allocationId, subgraphDeploymentID); } + /** + * @notice See {ISubgraphService.setPauseGuardian} + */ function setPauseGuardian(address pauseGuardian, bool allowed) external override onlyOwner { _setPauseGuardian(pauseGuardian, allowed); } + /** + * @notice See {ISubgraphService.setRewardsDestination} + */ + function setRewardsDestination(address rewardsDestination) external { + _setRewardsDestination(msg.sender, rewardsDestination); + } + + /** + * @notice See {ISubgraphService.setMinimumProvisionTokens} + */ + function setMinimumProvisionTokens(uint256 minimumProvisionTokens) external override onlyOwner { + _setProvisionTokensRange(minimumProvisionTokens, type(uint256).max); + } + + /** + * @notice See {ISubgraphService.setMaximumDelegationRatio} + */ + function setMaximumDelegationRatio(uint32 maximumDelegationRatio) external override onlyOwner { + _setDelegationRatioRange(type(uint32).min, maximumDelegationRatio); + } + + /** + * @notice See {ISubgraphService.getAllocation} + */ function getAllocation(address allocationId) external view override returns (Allocation.State memory) { return allocations[allocationId]; } + /** + * @notice Get allocation data to calculate rewards issuance + * @dev Implements {IRewardsIssuer.getAllocationData} + * @dev Note that this is slightly different than {getAllocation}. It returns an + * unstructured subset of the allocation data, which is the minimum required to mint rewards. + * + * Should only be used by the {RewardsManager}. + * + * @param allocationId The allocation Id + * @return indexer The indexer address + * @return subgraphDeploymentId Subgraph deployment id for the allocation + * @return tokens Amount of allocated tokens + * @return accRewardsPerAllocatedToken Rewards snapshot + */ function getAllocationData( address allocationId ) external view override returns (address, bytes32, uint256, uint256) { @@ -194,25 +372,67 @@ contract SubgraphService is ); } + /** + * @notice See {ISubgraphService.getLegacyAllocation} + */ function getLegacyAllocation(address allocationId) external view override returns (LegacyAllocation.State memory) { return legacyAllocations[allocationId]; } + /** + * @notice See {ISubgraphService.encodeAllocationProof} + */ function encodeAllocationProof(address indexer, address allocationId) external view override returns (bytes32) { return _encodeAllocationProof(indexer, allocationId); } // -- Data service parameter getters -- + /** + * @notice Getter for the accepted thawing period range for provisions + * @dev This override ensures {ProvisionManager} uses the thawing period from the {DisputeManager} + * @return min The minimum thawing period which is defined by {DisputeManager-getDisputePeriod} + * @return max The maximum is unbounded + */ function _getThawingPeriodRange() internal view override returns (uint64 min, uint64 max) { uint64 disputePeriod = _disputeManager().getDisputePeriod(); return (disputePeriod, type(uint64).max); } + /** + * @notice Getter for the accepted verifier cut range for provisions + * @return min The minimum verifier cut which is defined by {DisputeManager-getVerifierCut} + * @return max The maximum is unbounded + */ function _getVerifierCutRange() internal view override returns (uint32 min, uint32 max) { uint32 verifierCut = _disputeManager().getVerifierCut(); return (verifierCut, type(uint32).max); } + /** + * @notice Collect query fees + * Stake equal to the amount being collected times the `stakeToFeesRatio` is locked into a stake claim. + * This claim can be released at a later stage once expired. + * + * It's important to note that before collecting this function will attempt to release any expired stake claims. + * This could lead to an out of gas error if there are too many expired claims. In that case, the indexer will need to + * manually release the claims, see {IDataServiceFees-releaseStake}, before attempting to collect again. + * + * @dev This function is the equivalent of the legacy `collect` function for query fees. + * @dev Uses the {TAPCollector} to collect payment from Graph Horizon payments protocol. + * Fees are distributed to service provider and delegators by {GraphPayments}, though curators + * share is distributed by this function. + * + * Query fees can be collected on closed allocations. + * + * Requirements: + * - Indexer must have enough available tokens to lock as economic security for fees + * + * Emits a {StakeClaimsReleased} event, and a {StakeClaimReleased} event for each claim released. + * Emits a {StakeClaimLocked} event. + * Emits a {QueryFeesCollected} event. + * + * @param _data Encoded data containing a signed RAV + */ function _collectQueryFees(bytes memory _data) private returns (uint256 feesCollected) { ITAPCollector.SignedRAV memory signedRav = abi.decode(_data, (ITAPCollector.SignedRAV)); address indexer = signedRav.rav.serviceProvider; @@ -264,6 +484,11 @@ contract SubgraphService is return tokensCollected; } + /** + * @notice Gets the payment cuts for query fees + * Checks if the subgraph is curated and adjusts the curation cut accordingly + * @param _subgraphDeploymentId The subgraph deployment id + */ function _getQueryFeePaymentCuts(bytes32 _subgraphDeploymentId) private view returns (PaymentCuts memory) { PaymentCuts memory queryFeePaymentCuts = paymentCuts[IGraphPayments.PaymentTypes.QueryFee]; diff --git a/packages/subgraph-service/contracts/SubgraphServiceStorage.sol b/packages/subgraph-service/contracts/SubgraphServiceStorage.sol index 9d2ae9966..50e72b417 100644 --- a/packages/subgraph-service/contracts/SubgraphServiceStorage.sol +++ b/packages/subgraph-service/contracts/SubgraphServiceStorage.sol @@ -8,8 +8,7 @@ abstract contract SubgraphServiceV1Storage { /// @notice Service providers registered in the data service mapping(address indexer => ISubgraphService.Indexer details) public indexers; - // -- Fees -- - // multiplier for how many tokens back collected query fees + ///@notice Multiplier for how many tokens back collected query fees uint256 public stakeToFeesRatio; /// @notice The fees cut taken by the subgraph service diff --git a/packages/subgraph-service/contracts/interfaces/IDisputeManager.sol b/packages/subgraph-service/contracts/interfaces/IDisputeManager.sol index 27bd0418f..0da818cfa 100644 --- a/packages/subgraph-service/contracts/interfaces/IDisputeManager.sol +++ b/packages/subgraph-service/contracts/interfaces/IDisputeManager.sol @@ -5,14 +5,15 @@ pragma solidity 0.8.26; import { Attestation } from "../libraries/Attestation.sol"; interface IDisputeManager { - // -- Dispute -- + /// @notice Types of disputes that can be created enum DisputeType { Null, IndexingDispute, QueryDispute } + /// @notice Status of a dispute enum DisputeStatus { Null, Accepted, @@ -22,23 +23,58 @@ interface IDisputeManager { Cancelled } - // Disputes contain info necessary for the Arbitrator to verify and resolve + /// @notice Disputes contain info necessary for the Arbitrator to verify and resolve struct Dispute { + // Indexer that is being disputed address indexer; + // Fisherman that created the dispute address fisherman; + // Amount of tokens deposited by the fisherman uint256 deposit; + // Link to a related dispute, used when creating dispute via conflicting attestations bytes32 relatedDisputeId; + // Type of dispute DisputeType disputeType; + // Status of the dispute DisputeStatus status; + // Timestamp when the dispute was created uint256 createdAt; } - // -- Events -- + /** + * @notice Emitted when arbitrator is set. + * @param arbitrator The address of the arbitrator. + */ event ArbitratorSet(address indexed arbitrator); + + /** + * @notice Emitted when dispute period is set. + * @param disputePeriod The dispute period in seconds. + */ event DisputePeriodSet(uint64 disputePeriod); + + /** + * @notice Emitted when minimum deposit is set. + * @param minimumDeposit The minimum deposit required to create a dispute. + */ event MinimumDepositSet(uint256 minimumDeposit); + + /** + * @notice Emitted when max slashing cut is set. + * @param maxSlashingCut The maximum slashing cut that can be set. + */ event MaxSlashingCutSet(uint32 maxSlashingCut); + + /** + * @notice Emitted when fisherman reward cut is set. + * @param fishermanRewardCut The fisherman reward cut. + */ event FishermanRewardCutSet(uint32 fishermanRewardCut); + + /** + * @notice Emitted when subgraph service is set. + * @param subgraphService The address of the subgraph service. + */ event SubgraphServiceSet(address indexed subgraphService); /** diff --git a/packages/subgraph-service/contracts/interfaces/ISubgraphService.sol b/packages/subgraph-service/contracts/interfaces/ISubgraphService.sol index 868e63fca..d4b51999b 100644 --- a/packages/subgraph-service/contracts/interfaces/ISubgraphService.sol +++ b/packages/subgraph-service/contracts/interfaces/ISubgraphService.sol @@ -7,18 +7,40 @@ import { IGraphPayments } from "@graphprotocol/horizon/contracts/interfaces/IGra import { Allocation } from "../libraries/Allocation.sol"; import { LegacyAllocation } from "../libraries/LegacyAllocation.sol"; +/** + * @title Interface for the {SubgraphService} contract + * @dev This interface extends {IDataServiceFees} and {IDataService}. + * @notice The Subgraph Service is a data service built on top of Graph Horizon that supports the use case of + * subgraph indexing and querying. The {SubgraphService} contract implements the flows described in the Data + * Service framework to allow indexers to register as subgraph service providers, create allocations to signal + * their commitment to index a subgraph, and collect fees for indexing and querying services. + */ interface ISubgraphService is IDataServiceFees { + /// @notice Contains details for each indexer struct Indexer { + // Timestamp when the indexer registered uint256 registeredAt; + // The URL where the indexer can be reached at for queries string url; + // The indexer's geo location, expressed as a geo hash string geoHash; } + /// @notice Payment cut definitions for each payment type struct PaymentCuts { + // The cut the service provider takes from the payment uint128 serviceCut; + // The cut curators take from the payment uint128 curationCut; } + /** + * @notice Emitted when a subgraph service collects query fees from Graph Payments + * @param serviceProvider The address of the service provider + * @param tokensCollected The amount of tokens collected + * @param tokensCurators The amount of tokens curators receive + * @param tokensSubgraphService The amount of tokens the subgraph service receives + */ event QueryFeesCollected( address indexed serviceProvider, uint256 tokensCollected, @@ -26,22 +48,116 @@ interface ISubgraphService is IDataServiceFees { uint256 tokensSubgraphService ); + /** + * @notice Thrown when an indexer tries to register with an empty URL + */ error SubgraphServiceEmptyUrl(); - error SubgraphServiceInvalidPaymentType(IGraphPayments.PaymentTypes feeType); + + /** + * @notice Thrown when an indexer tries to register but they are already registered + */ error SubgraphServiceIndexerAlreadyRegistered(); + + /** + * @notice Thrown when an indexer tries to perform an operation but they are not registered + * @param indexer The address of the indexer that is not registered + */ error SubgraphServiceIndexerNotRegistered(address indexer); + + /** + * @notice Thrown when an indexer tries to collect fees for an unsupported payment type + * @param paymentType The payment type that is not supported + */ + error SubgraphServiceInvalidPaymentType(IGraphPayments.PaymentTypes paymentType); + + /** + * @notice Thrown when the contract GRT balance is inconsistent with the payment amount collected + * from Graph Payments + * @param balanceBefore The contract GRT balance before the collection + * @param balanceAfter The contract GRT balance after the collection + * @param tokensCollected The amount of tokens collected + */ error SubgraphServiceInconsistentCollection(uint256 balanceBefore, uint256 balanceAfter, uint256 tokensCollected); + /** + * @notice Initialize the contract + * @param minimumProvisionTokens The minimum amount of provisioned tokens required to create an allocation + * @param maximumDelegationRatio The maximum delegation ratio allowed for an allocation + */ function initialize(uint256 minimumProvisionTokens, uint32 maximumDelegationRatio) external; + + /** + * @notice Change the amount of tokens in an allocation + * @dev Requirements: + * - The indexer must be registered + * - The provision must be valid according to the subgraph service rules + * - `tokens` must be different from the current allocation size + * - The indexer must have enough available tokens to allocate if they are upsizing the allocation + * + * Emits a {AllocationResized} event. + * + * See {AllocationManager-_resizeAllocation} for more details. + * + * @param indexer The address of the indexer + * @param allocationId The id of the allocation + * @param tokens The new amount of tokens in the allocation + */ function resizeAllocation(address indexer, address allocationId, uint256 tokens) external; - function migrateLegacyAllocation(address indexer, address allocationId, bytes32 subgraphDeploymentID) external; + /** + * @notice Imports a legacy allocation id into the subgraph service + * This is a governor only action that is required to prevent indexers from re-using allocation ids from the + * legacy staking contract. + * @param indexer The address of the indexer + * @param allocationId The id of the allocation + * @param subgraphDeploymentId The id of the subgraph deployment + */ + function migrateLegacyAllocation(address indexer, address allocationId, bytes32 subgraphDeploymentId) external; + /** + * @notice Sets a pause guardian + * @param pauseGuardian The address of the pause guardian + * @param allowed True if the pause guardian is allowed to pause the contract, false otherwise + */ function setPauseGuardian(address pauseGuardian, bool allowed) external; + /** + * @notice Sets the minimum amount of provisioned tokens required to create an allocation + * @param minimumProvisionTokens The minimum amount of provisioned tokens required to create an allocation + */ + function setMinimumProvisionTokens(uint256 minimumProvisionTokens) external; + + /** + * @notice Sets the maximum delegation ratio allowed for an allocation + * @param maximumDelegationRatio The maximum delegation ratio allowed for an allocation + */ + function setMaximumDelegationRatio(uint32 maximumDelegationRatio) external; + + /** + * @notice Sets the rewards destination for an indexer to receive indexing rewards + * @dev Emits a {RewardsDestinationSet} event + * @param rewardsDestination The address where indexing rewards should be sent + */ + function setRewardsDestination(address rewardsDestination) external; + + /** + * @notice Gets the details of an allocation + * For legacy allocations use {getLegacyAllocation} + * @param allocationId The id of the allocation + */ function getAllocation(address allocationId) external view returns (Allocation.State memory); + /** + * @notice Gets the details of a legacy allocation + * For non-legacy allocations use {getAllocation} + * @param allocationId The id of the allocation + */ function getLegacyAllocation(address allocationId) external view returns (LegacyAllocation.State memory); + /** + * @notice Encodes the allocation proof for EIP712 signing + * @param indexer The address of the indexer + * @param allocationId The id of the allocation + */ function encodeAllocationProof(address indexer, address allocationId) external view returns (bytes32); } diff --git a/packages/subgraph-service/contracts/libraries/Allocation.sol b/packages/subgraph-service/contracts/libraries/Allocation.sol index 34c876135..0df8f6e21 100644 --- a/packages/subgraph-service/contracts/libraries/Allocation.sol +++ b/packages/subgraph-service/contracts/libraries/Allocation.sol @@ -1,25 +1,66 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.26; +/** + * @title Allocation library + * @notice A library to handle Allocations. + */ library Allocation { using Allocation for State; + /** + * @notice Allocation details + */ struct State { + // Indexer that owns the allocation address indexer; + // Subgraph deployment id the allocation is for bytes32 subgraphDeploymentId; + // Number of tokens allocated uint256 tokens; + // Timestamp when the allocation was created uint256 createdAt; + // Timestamp when the allocation was closed uint256 closedAt; + // Timestamp when the last POI was presented uint256 lastPOIPresentedAt; + // Accumulated rewards per allocated token uint256 accRewardsPerAllocatedToken; + // Accumulated rewards per allocated token that are pending to be claimed + // due to an allocation resize uint256 accRewardsPending; } + /** + * @notice Thrown when attempting to create an allocation with an existing id + * @param allocationId The allocation id + */ error AllocationAlreadyExists(address allocationId); + + /** + * @notice Thrown when trying to perform an operation on a non-existent allocation + * @param allocationId The allocation id + */ error AllocationDoesNotExist(address allocationId); + + /** + * @notice Thrown when trying to perform an operation on a closed allocation + * @param allocationId The allocation id + * @param closedAt The timestamp when the allocation was closed + */ error AllocationClosed(address allocationId, uint256 closedAt); - error AllocationZeroTokens(address allocationId); + /** + * @notice Create a new allocation + * @dev Requirements: + * - The allocation must not exist + * @param self The allocation list mapping + * @param indexer The indexer that owns the allocation + * @param allocationId The allocation id + * @param subgraphDeploymentId The subgraph deployment id the allocation is for + * @param tokens The number of tokens allocated + * @param accRewardsPerAllocatedToken The initial accumulated rewards per allocated token + */ function create( mapping(address => State) storage self, address indexer, @@ -46,8 +87,14 @@ library Allocation { return allocation; } - // Update POI timestamp and take rewards snapshot - // For stale POIs this ensures the rewards are not collected with the next valid POI + /** + * @notice Present a POI for an allocation + * @dev It only updates the last POI presented timestamp. + * Requirements: + * - The allocation must be open + * @param self The allocation list mapping + * @param allocationId The allocation id + */ function presentPOI(mapping(address => State) storage self, address allocationId) internal returns (State memory) { State storage allocation = _get(self, allocationId); require(allocation.isOpen(), AllocationClosed(allocationId, allocation.closedAt)); @@ -56,6 +103,14 @@ library Allocation { return allocation; } + /** + * @notice Update the accumulated rewards per allocated token for an allocation + * @dev Requirements: + * - The allocation must be open + * @param self The allocation list mapping + * @param allocationId The allocation id + * @param accRewardsPerAllocatedToken The new accumulated rewards per allocated token + */ function snapshotRewards( mapping(address => State) storage self, address allocationId, @@ -68,6 +123,13 @@ library Allocation { return allocation; } + /** + * @notice Update the accumulated rewards pending to be claimed for an allocation + * @dev Requirements: + * - The allocation must be open + * @param self The allocation list mapping + * @param allocationId The allocation id + */ function clearPendingRewards( mapping(address => State) storage self, address allocationId @@ -79,6 +141,13 @@ library Allocation { return allocation; } + /** + * @notice Close an allocation + * @dev Requirements: + * - The allocation must be open + * @param self The allocation list mapping + * @param allocationId The allocation id + */ function close(mapping(address => State) storage self, address allocationId) internal returns (State memory) { State storage allocation = _get(self, allocationId); require(allocation.isOpen(), AllocationClosed(allocationId, allocation.closedAt)); @@ -87,22 +156,45 @@ library Allocation { return allocation; } + /** + * @notice Get an allocation + * @param self The allocation list mapping + * @param allocationId The allocation id + */ function get(mapping(address => State) storage self, address allocationId) internal view returns (State memory) { return _get(self, allocationId); } + /** + * @notice Checks if an allocation exists + * @param self The allocation + */ function exists(State memory self) internal pure returns (bool) { return self.createdAt != 0; } + /** + * @notice Checks if an allocation is open + * @param self The allocation + */ function isOpen(State memory self) internal pure returns (bool) { return self.exists() && self.closedAt == 0; } + /** + * @notice Checks if an allocation is closed + * @param self The allocation + */ function isAltruistic(State memory self) internal pure returns (bool) { return self.exists() && self.tokens == 0; } + /** + * @notice Get the allocation for an allocation id + * @dev Reverts if the allocation does not exist + * @param self The allocation list mapping + * @param allocationId The allocation id + */ function _get(mapping(address => State) storage self, address allocationId) private view returns (State storage) { State storage allocation = self[allocationId]; require(allocation.exists(), AllocationDoesNotExist(allocationId)); diff --git a/packages/subgraph-service/contracts/libraries/Attestation.sol b/packages/subgraph-service/contracts/libraries/Attestation.sol index b08802ec3..7926ca30a 100644 --- a/packages/subgraph-service/contracts/libraries/Attestation.sol +++ b/packages/subgraph-service/contracts/libraries/Attestation.sol @@ -1,15 +1,19 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.26; +/** + * @title Attestation library + * @notice A library to handle Attestation. + */ library Attestation { - // Receipt content sent from the service provider in response to request + /// @notice Receipt content sent from the service provider in response to request struct Receipt { bytes32 requestCID; bytes32 responseCID; bytes32 subgraphDeploymentId; } - // Attestation sent from the service provider in response to a request + /// @notice Attestation sent from the service provider in response to a request struct State { bytes32 requestCID; bytes32 responseCID; @@ -19,7 +23,7 @@ library Attestation { uint8 v; } - // Attestation size is the sum of the receipt (96) + signature (65) + /// @notice Attestation size is the sum of the receipt (96) + signature (65) uint256 private constant ATTESTATION_SIZE_BYTES = RECEIPT_SIZE_BYTES + SIG_SIZE_BYTES; uint256 private constant RECEIPT_SIZE_BYTES = 96; diff --git a/packages/subgraph-service/contracts/libraries/LegacyAllocation.sol b/packages/subgraph-service/contracts/libraries/LegacyAllocation.sol index 604a3bd78..81e6bcdcb 100644 --- a/packages/subgraph-service/contracts/libraries/LegacyAllocation.sol +++ b/packages/subgraph-service/contracts/libraries/LegacyAllocation.sol @@ -1,42 +1,93 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.26; +/** + * @title LegacyAllocation library + * @notice A library to handle legacy Allocations. + */ library LegacyAllocation { using LegacyAllocation for State; + /** + * @notice Legacy allocation details + * @dev Note that we are only storing the indexer and subgraphDeploymentId. The main point of tracking legacy allocations + * is to prevent them from being re used on the Subgraph Service. We don't need to store the rest of the allocation details. + */ struct State { address indexer; - bytes32 subgraphDeploymentID; + bytes32 subgraphDeploymentId; } + /** + * @notice Thrown when attempting to migrate an allocation with an existing id + * @param allocationId The allocation id + */ error LegacyAllocationExists(address allocationId); + + /** + * @notice Thrown when trying to get a non-existent allocation + * @param allocationId The allocation id + */ error LegacyAllocationDoesNotExist(address allocationId); + + /** + * @notice Thrown when trying to migrate an allocation that has already been migrated + * @param allocationId The allocation id + */ error LegacyAllocationAlreadyMigrated(address allocationId); + /** + * @notice Migrate a legacy allocation + * @dev Requirements: + * - The allocation must not exist + * @param self The legacy allocation list mapping + * @param indexer The indexer that owns the allocation + * @param allocationId The allocation id + * @param subgraphDeploymentId The subgraph deployment id the allocation is for + */ function migrate( mapping(address => State) storage self, address indexer, address allocationId, - bytes32 subgraphDeploymentID + bytes32 subgraphDeploymentId ) internal { require(!self[allocationId].exists(), LegacyAllocationExists(allocationId)); - State memory allocation = State({ indexer: indexer, subgraphDeploymentID: subgraphDeploymentID }); + State memory allocation = State({ indexer: indexer, subgraphDeploymentId: subgraphDeploymentId }); self[allocationId] = allocation; } + /** + * @notice Get a legacy allocation + * @param self The legacy allocation list mapping + * @param allocationId The allocation id + */ function get(mapping(address => State) storage self, address allocationId) internal view returns (State memory) { return _get(self, allocationId); } + /** + * @notice Revert if a legacy allocation exists + * @param self The legacy allocation list mapping + * @param allocationId The allocation id + */ function revertIfExists(mapping(address => State) storage self, address allocationId) internal view { require(!self[allocationId].exists(), LegacyAllocationExists(allocationId)); } + /** + * @notice Check if a legacy allocation exists + * @param self The legacy allocation + */ function exists(State memory self) internal pure returns (bool) { return self.indexer != address(0); } + /** + * @notice Get a legacy allocation + * @param self The legacy allocation list mapping + * @param allocationId The allocation id + */ function _get(mapping(address => State) storage self, address allocationId) private view returns (State storage) { State storage allocation = self[allocationId]; require(allocation.exists(), LegacyAllocationDoesNotExist(allocationId)); diff --git a/packages/subgraph-service/contracts/utilities/AllocationManager.sol b/packages/subgraph-service/contracts/utilities/AllocationManager.sol index 5a4a9c58f..ec5d869a1 100644 --- a/packages/subgraph-service/contracts/utilities/AllocationManager.sol +++ b/packages/subgraph-service/contracts/utilities/AllocationManager.sol @@ -14,6 +14,12 @@ import { LegacyAllocation } from "../libraries/LegacyAllocation.sol"; import { PPMMath } from "@graphprotocol/horizon/contracts/libraries/PPMMath.sol"; import { ProvisionTracker } from "@graphprotocol/horizon/contracts/data-service/libraries/ProvisionTracker.sol"; +/** + * @title AllocationManager contract + * @notice A helper contract implementing allocation lifecycle management. + * Allows opening, resizing, and closing allocations, as well as collecting indexing rewards by presenting a Proof + * of Indexing (POI). + */ abstract contract AllocationManager is EIP712Upgradeable, GraphDirectory, AllocationManagerV1Storage { using ProvisionTracker for mapping(address => uint256); using Allocation for mapping(address => Allocation.State); @@ -21,14 +27,16 @@ abstract contract AllocationManager is EIP712Upgradeable, GraphDirectory, Alloca using LegacyAllocation for mapping(address => LegacyAllocation.State); using PPMMath for uint256; - // -- Immutables -- + ///@dev EIP712 typehash for allocation proof bytes32 private immutable EIP712_ALLOCATION_PROOF_TYPEHASH = keccak256("AllocationIdProof(address indexer,address allocationId)"); /** - * @dev Emitted when `indexer` allocated `tokens` amount to `subgraphDeploymentId` - * during `epoch`. - * `allocationId` indexer derived address used to identify the allocation. + * @notice Emitted when an indexer creates an allocation + * @param indexer The address of the indexer + * @param allocationId The id of the allocation + * @param subgraphDeploymentId The id of the subgraph deployment + * @param tokens The amount of tokens allocated */ event AllocationCreated( address indexed indexer, @@ -37,6 +45,16 @@ abstract contract AllocationManager is EIP712Upgradeable, GraphDirectory, Alloca uint256 tokens ); + /** + * @notice Emitted when an indexer collects indexing rewards for an allocation + * @param indexer The address of the indexer + * @param allocationId The id of the allocation + * @param subgraphDeploymentId The id of the subgraph deployment + * @param tokensRewards The amount of tokens collected + * @param tokensIndexerRewards The amount of tokens collected for the indexer + * @param tokensDelegationRewards The amount of tokens collected for delegators + * @param poi The POI presented + */ event IndexingRewardsCollected( address indexed indexer, address indexed allocationId, @@ -47,6 +65,14 @@ abstract contract AllocationManager is EIP712Upgradeable, GraphDirectory, Alloca bytes32 poi ); + /** + * @notice Emitted when an indexer resizes an allocation + * @param indexer The address of the indexer + * @param allocationId The id of the allocation + * @param subgraphDeploymentId The id of the subgraph deployment + * @param newTokens The new amount of tokens allocated + * @param oldTokens The old amount of tokens allocated + */ event AllocationResized( address indexed indexer, address indexed allocationId, @@ -56,8 +82,11 @@ abstract contract AllocationManager is EIP712Upgradeable, GraphDirectory, Alloca ); /** - * @dev Emitted when `indexer` closes an allocation with id `allocationId`. - * An amount of `tokens` get unallocated from `subgraphDeploymentId`. + * @dev Emitted when an indexer closes an allocation + * @param indexer The address of the indexer + * @param allocationId The id of the allocation + * @param subgraphDeploymentId The id of the subgraph deployment + * @param tokens The amount of tokens allocated */ event AllocationClosed( address indexed indexer, @@ -66,37 +95,99 @@ abstract contract AllocationManager is EIP712Upgradeable, GraphDirectory, Alloca uint256 tokens ); + /** + * @notice Emitted when a legacy allocation is migrated into the subgraph service + * @param indexer The address of the indexer + * @param allocationId The id of the allocation + * @param subgraphDeploymentId The id of the subgraph deployment + */ event LegacyAllocationMigrated( address indexed indexer, address indexed allocationId, bytes32 indexed subgraphDeploymentId ); + /** + * @notice Emitted when an indexer sets a new indexing rewards destination + * @param indexer The address of the indexer + * @param rewardsDestination The address where indexing rewards should be sent + */ event RewardsDestinationSet(address indexed indexer, address indexed rewardsDestination); + /** + * @notice Thrown when an allocation proof is invalid + * Both `signer` and `allocationId` should match for a valid proof. + * @param signer The address that signed the proof + * @param allocationId The id of the allocation + */ error AllocationManagerInvalidAllocationProof(address signer, address allocationId); - error AllocationManagerInvalidAllocationId(); + + /** + * @notice Thrown when attempting to create an allocation with a zero allocation id + */ + error AllocationManagerInvalidZeroAllocationId(); + + /** + * @notice Thrown when attempting to create an allocation with zero tokens + * @param allocationId The id of the allocation + */ error AllocationManagerZeroTokensAllocation(address allocationId); + + /** + * @notice Thrown when attempting to collect indexing rewards on a closed allocationl + * @param allocationId The id of the allocation + */ error AllocationManagerAllocationClosed(address allocationId); + + /** + * @notice Thrown when attempting to resize an allocation with the same size + * @param allocationId The id of the allocation + * @param tokens The amount of tokens + */ error AllocationManagerAllocationSameSize(address allocationId, uint256 tokens); - error AllocationManagerInvalidZeroPOI(); + /** + * @notice Initializes the contract and parent contracts + */ function __AllocationManager_init(string memory name, string memory version) internal onlyInitializing { __EIP712_init(name, version); __AllocationManager_init_unchained(name, version); } + /** + * @notice Initializes the contract + */ function __AllocationManager_init_unchained(string memory name, string memory version) internal onlyInitializing {} - function setRewardsDestination(address rewardsDestination) external { - _setRewardsDestination(msg.sender, rewardsDestination); - } - + /** + * @notice Imports a legacy allocation id into the subgraph service + * This is a governor only action that is required to prevent indexers from re-using allocation ids from the + * legacy staking contract. + * @param _indexer The address of the indexer + * @param _allocationId The id of the allocation + * @param _subgraphDeploymentId The id of the subgraph deployment + */ function _migrateLegacyAllocation(address _indexer, address _allocationId, bytes32 _subgraphDeploymentId) internal { legacyAllocations.migrate(_indexer, _allocationId, _subgraphDeploymentId); emit LegacyAllocationMigrated(_indexer, _allocationId, _subgraphDeploymentId); } + /** + * @notice Create an allocation + * @dev The `_allocationProof` is a 65-bytes Ethereum signed message of `keccak256(indexerAddress,allocationId)` + * + * Requirements: + * - `_allocationId` must not be the zero address + * + * Emits a {AllocationCreated} event + * + * @param _indexer The address of the indexer + * @param _allocationId The id of the allocation to be created + * @param _subgraphDeploymentId The subgraph deployment Id + * @param _tokens The amount of tokens to allocate + * @param _allocationProof Signed proof of allocation id address ownership + * @param _delegationRatio The delegation ratio to consider when locking tokens + */ function _allocate( address _indexer, address _allocationId, @@ -105,23 +196,23 @@ abstract contract AllocationManager is EIP712Upgradeable, GraphDirectory, Alloca bytes memory _allocationProof, uint32 _delegationRatio ) internal returns (Allocation.State memory) { - require(_allocationId != address(0), AllocationManagerInvalidAllocationId()); + require(_allocationId != address(0), AllocationManagerInvalidZeroAllocationId()); _verifyAllocationProof(_indexer, _allocationId, _allocationProof); // Ensure allocation id is not reused - // need to check both subgraph service (on create()) and legacy allocations + // need to check both subgraph service (on allocations.create()) and legacy allocations legacyAllocations.revertIfExists(_allocationId); Allocation.State memory allocation = allocations.create( _indexer, _allocationId, _subgraphDeploymentId, _tokens, - // allos can be resized now, so we need to always take snapshot _graphRewardsManager().onSubgraphAllocationUpdate(_subgraphDeploymentId) ); // Check that the indexer has enough tokens available + // Note that the delegation ratio ensures overdelegation cannot be used allocationProvisionTracker.lock(_graphStaking(), _indexer, _tokens, _delegationRatio); // Update total allocated tokens for the subgraph deployment @@ -133,17 +224,32 @@ abstract contract AllocationManager is EIP712Upgradeable, GraphDirectory, Alloca return allocation; } - // Update POI timestamp and take rewards snapshot even for 0 rewards - // This ensures the rewards are actually skipped and not collected with the next valid POI + /** + * @notice Present a POI to collect indexing rewards for an allocation + * This function will mint indexing rewards using the {RewardsManager} and distribute them to the indexer and delegators. + * + * To qualify for indexing rewards: + * - POI must be non-zero + * - POI must not be stale, i.e: older than `maxPOIStaleness` + * - allocation must not be altruistic (allocated tokens = 0) + * + * Note that indexers are required to periodically (at most every `maxPOIStaleness`) present POIs to collect rewards. + * Rewards will not be issued to stale POIs, which means that indexers are advised to present a zero POI if they are + * unable to present a valid one to prevent being locked out of future rewards. + * + * Emits a {IndexingRewardsCollected} event. + * + * @param _data Encoded data containing: + * - address `allocationId`: The id of the allocation to collect rewards for + * - bytes32 `poi`: The POI being presented + */ function _collectIndexingRewards(bytes memory _data) internal returns (uint256) { (address allocationId, bytes32 poi) = abi.decode(_data, (address, bytes32)); Allocation.State memory allocation = allocations.get(allocationId); + require(allocation.isOpen(), AllocationManagerAllocationClosed(allocationId)); - // Mint indexing rewards. To qualify: - // - POI must be non-zero - // - POI must not be stale, i.e: older than maxPOIStaleness - // - allocation must not be altruistic (tokens = 0) + // Mint indexing rewards if all conditions are met uint256 timeSinceLastPOI = block.number - Math.max(allocation.createdAt, allocation.lastPOIPresentedAt); uint256 tokensRewards = (timeSinceLastPOI <= maxPOIStaleness && poi != bytes32(0) && !allocation.isAltruistic()) ? _graphRewardsManager().takeRewards(allocationId) @@ -173,7 +279,6 @@ abstract contract AllocationManager is EIP712Upgradeable, GraphDirectory, Alloca } // Distribute rewards to delegators - // TODO: remove the uint8 cast when PRs are merged uint256 delegatorCut = _graphStaking().getDelegationFeeCut( allocation.indexer, address(this), @@ -208,6 +313,21 @@ abstract contract AllocationManager is EIP712Upgradeable, GraphDirectory, Alloca return tokensRewards; } + /** + * @notice Resize an allocation + * @dev Will lock or release tokens in the provision tracker depending on the new allocation size. + * Rewards accrued but not issued before the resize will be accounted for as pending rewards. + * These will be paid out when the indexer presents a POI. + * + * Requirements: + * - `_tokens` must be different from the current allocation size + * + * Emits a {AllocationResized} event. + * + * @param _allocationId The id of the allocation to be resized + * @param _tokens The new amount of tokens to allocate + * @param _delegationRatio The delegation ratio to consider when locking tokens + */ function _resizeAllocation( address _allocationId, uint256 _tokens, @@ -245,6 +365,17 @@ abstract contract AllocationManager is EIP712Upgradeable, GraphDirectory, Alloca return allocations[_allocationId]; } + /** + * @notice Close an allocation + * Does not require presenting a POI, use {_collectIndexingRewards} to present a POI and collect rewards + * @dev Note that allocations are nowlong lived. All service payments, including indexing rewards, should be collected periodically + * without the need of closing the allocation. Allocations should only be closed when indexers want to reclaim the allocated + * tokens for other purposes. + * + * Emits a {AllocationClosed} event + * + * @param _allocationId The id of the allocation to be closed + */ function _closeAllocation(address _allocationId) internal returns (Allocation.State memory) { Allocation.State memory allocation = allocations.get(_allocationId); @@ -266,28 +397,51 @@ abstract contract AllocationManager is EIP712Upgradeable, GraphDirectory, Alloca return allocations[_allocationId]; } + /** + * @notice Sets the rewards destination for an indexer to receive indexing rewards + * @dev Emits a {RewardsDestinationSet} event + * @param _rewardsDestination The address where indexing rewards should be sent + */ function _setRewardsDestination(address _indexer, address _rewardsDestination) internal { rewardsDestination[_indexer] = _rewardsDestination; emit RewardsDestinationSet(_indexer, _rewardsDestination); } + /** + * @notice Gets the details of an allocation + * @param _allocationId The id of the allocation + */ function _getAllocation(address _allocationId) internal view returns (Allocation.State memory) { return allocations.get(_allocationId); } + /** + * @notice Gets the details of a legacy allocation + * @param _allocationId The id of the legacy allocation + */ function _getLegacyAllocation(address _allocationId) internal view returns (LegacyAllocation.State memory) { return legacyAllocations.get(_allocationId); } - // -- Allocation Proof Verification -- - // Caller must prove that they own the private key for the allocationId address - // The proof is an EIP712 signed message of (indexer,allocationId) + /** + * @notice Verifies ownsership of an allocation id by verifying an EIP712 allocation proof + * @dev Requirements: + * - Signer must be the allocation id address + * @param _indexer The address of the indexer + * @param _allocationId The id of the allocation + * @param _proof The EIP712 proof, an EIP712 signed message of (indexer,allocationId) + */ function _verifyAllocationProof(address _indexer, address _allocationId, bytes memory _proof) internal view { bytes32 digest = _encodeAllocationProof(_indexer, _allocationId); address signer = ECDSA.recover(digest, _proof); require(signer == _allocationId, AllocationManagerInvalidAllocationProof(signer, _allocationId)); } + /** + * @notice Encodes the allocation proof for EIP712 signing + * @param _indexer The address of the indexer + * @param _allocationId The id of the allocation + */ function _encodeAllocationProof(address _indexer, address _allocationId) internal view returns (bytes32) { return _hashTypedDataV4(keccak256(abi.encode(EIP712_ALLOCATION_PROOF_TYPEHASH, _indexer, _allocationId))); } diff --git a/packages/subgraph-service/contracts/utilities/AllocationManagerStorage.sol b/packages/subgraph-service/contracts/utilities/AllocationManagerStorage.sol index 700c0243c..da812f5c1 100644 --- a/packages/subgraph-service/contracts/utilities/AllocationManagerStorage.sol +++ b/packages/subgraph-service/contracts/utilities/AllocationManagerStorage.sol @@ -5,14 +5,19 @@ import { Allocation } from "../libraries/Allocation.sol"; import { LegacyAllocation } from "../libraries/LegacyAllocation.sol"; abstract contract AllocationManagerV1Storage { + /// @notice Allocation details mapping(address allocationId => Allocation.State allocation) public allocations; - mapping(address indexer => uint256 tokens) public allocationProvisionTracker; + + /// @notice Legacy allocation details mapping(address allocationId => LegacyAllocation.State allocation) public legacyAllocations; - /// @notice Maximum amount of since last POI was presented to qualify for indexing rewards + /// @notice Tracks allocated tokens per indexer + mapping(address indexer => uint256 tokens) public allocationProvisionTracker; + + /// @notice Maximum amount of time, in seconds, allowed between presenting POIs to qualify for indexing rewards uint256 public maxPOIStaleness; - /// @dev Destination of accrued rewards + /// @notice Destination of accrued indexing rewards mapping(address indexer => address destination) public rewardsDestination; /// @notice Track total tokens allocated per subgraph deployment diff --git a/packages/subgraph-service/contracts/utilities/AttestationManager.sol b/packages/subgraph-service/contracts/utilities/AttestationManager.sol index c6ed521b6..63ebcf9a1 100644 --- a/packages/subgraph-service/contracts/utilities/AttestationManager.sol +++ b/packages/subgraph-service/contracts/utilities/AttestationManager.sol @@ -7,20 +7,39 @@ import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import { Attestation } from "../libraries/Attestation.sol"; +/** + * @title AttestationManager contract + * @notice A helper contract implementing attestation verification. + * Uses a custom implementation of EIP712 for backwards compatibility with attestations. + */ abstract contract AttestationManager is Initializable, AttestationManagerV1Storage { + /// @notice EIP712 type hash for Receipt struct bytes32 private constant RECEIPT_TYPE_HASH = keccak256("Receipt(bytes32 requestCID,bytes32 responseCID,bytes32 subgraphDeploymentID)"); + /// @notice EIP712 domain type hash bytes32 private constant DOMAIN_TYPE_HASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)"); + + /// @notice EIP712 domain name bytes32 private constant DOMAIN_NAME_HASH = keccak256("Graph Protocol"); + + /// @notice EIP712 domain version bytes32 private constant DOMAIN_VERSION_HASH = keccak256("0"); + + /// @notice EIP712 domain salt bytes32 private constant DOMAIN_SALT = 0xa070ffb1cd7409649bf77822cce74495468e06dbfaef09556838bf188679b9c2; + /** + * @dev Initialize the AttestationManager contract and parent contracts + */ function __AttestationManager_init() internal onlyInitializing { __AttestationManager_init_unchained(); } + /** + * @dev Initialize the AttestationManager contract + */ function __AttestationManager_init_unchained() internal onlyInitializing { _domainSeparator = keccak256( abi.encode( diff --git a/packages/subgraph-service/contracts/utilities/AttestationManagerStorage.sol b/packages/subgraph-service/contracts/utilities/AttestationManagerStorage.sol index cfb925f5b..5bf57af65 100644 --- a/packages/subgraph-service/contracts/utilities/AttestationManagerStorage.sol +++ b/packages/subgraph-service/contracts/utilities/AttestationManagerStorage.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.26; abstract contract AttestationManagerV1Storage { + /// @dev EIP712 domain separator bytes32 internal _domainSeparator; /// @dev Gap to allow adding variables in future upgrades diff --git a/packages/subgraph-service/contracts/utilities/Directory.sol b/packages/subgraph-service/contracts/utilities/Directory.sol index 8bfaee861..920a0ae5e 100644 --- a/packages/subgraph-service/contracts/utilities/Directory.sol +++ b/packages/subgraph-service/contracts/utilities/Directory.sol @@ -6,20 +6,51 @@ import { ISubgraphService } from "../interfaces/ISubgraphService.sol"; import { ITAPCollector } from "@graphprotocol/horizon/contracts/interfaces/ITAPCollector.sol"; import { ICuration } from "@graphprotocol/contracts/contracts/curation/ICuration.sol"; +/** + * @title Directory contract + * @notice This contract is meant to be inherited by {SubgraphService} contract. + * It contains the addresses of the contracts that the contract interacts with. + * Uses immutable variables to minimize gas costs. + */ abstract contract Directory { + /// @notice The Subgraph Service contract address ISubgraphService private immutable SUBGRAPH_SERVICE; + + /// @notice The Dispute Manager contract address IDisputeManager private immutable DISPUTE_MANAGER; + + /// @notice The TAP Collector contract address + /// @dev Required to collect payments via Graph Horizon payments protocol ITAPCollector private immutable TAP_COLLECTOR; + + /// @notice The Curation contract address + /// @dev Required for curation fees distribution ICuration private immutable CURATION; + /** + * @notice Emitted when the Directory is initialized + * @param subgraphService The Subgraph Service contract address + * @param disputeManager The Dispute Manager contract address + * @param tapCollector The TAP Collector contract address + * @param curation The Curation contract address + */ event SubgraphServiceDirectoryInitialized( address subgraphService, address disputeManager, address tapCollector, address curation ); + + /** + * @notice Thrown when the caller is not the Dispute Manager + * @param caller The caller address + * @param disputeManager The Dispute Manager address + */ error DirectoryNotDisputeManager(address caller, address disputeManager); + /** + * @notice Checks that the caller is the Dispute Manager + */ modifier onlyDisputeManager() { require( msg.sender == address(DISPUTE_MANAGER), @@ -28,6 +59,13 @@ abstract contract Directory { _; } + /** + * @notice Constructor for the Directory contract + * @param subgraphService The Subgraph Service contract address + * @param disputeManager The Dispute Manager contract address + * @param tapCollector The TAP Collector contract address + * @param curation The Curation contract address + */ constructor(address subgraphService, address disputeManager, address tapCollector, address curation) { SUBGRAPH_SERVICE = ISubgraphService(subgraphService); DISPUTE_MANAGER = IDisputeManager(disputeManager); @@ -37,18 +75,30 @@ abstract contract Directory { emit SubgraphServiceDirectoryInitialized(subgraphService, disputeManager, tapCollector, curation); } + /** + * @notice Returns the Subgraph Service contract address + */ function _subgraphService() internal view returns (ISubgraphService) { return SUBGRAPH_SERVICE; } + /** + * @notice Returns the Dispute Manager contract address + */ function _disputeManager() internal view returns (IDisputeManager) { return DISPUTE_MANAGER; } + /** + * @notice Returns the TAP Collector contract address + */ function _tapCollector() internal view returns (ITAPCollector) { return TAP_COLLECTOR; } + /** + * @notice Returns the Curation contract address + */ function _curation() internal view returns (ICuration) { return CURATION; }