Skip to content

Commit ae910c4

Browse files
f: add createIndexingFeeDispute to DisputeManager
1 parent a2fe023 commit ae910c4

File tree

6 files changed

+146
-1
lines changed

6 files changed

+146
-1
lines changed

IndexingPaymentsTodo.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
### Still pending
22

33
* Make `agreementId` unique globally so that we don't need the full tuple (`payer`+`indexer`+`agreementId`) as key?
4+
* Reverse name of `SubgraphServiceIndexingAgreementAlreadyAllocated`?
45
* Update `DisputeManager` and Arbitration Charter to support disputing Indexing Fees.
56
* Support indexing agreement upgadeability, so that there is a mechanism to adjust the rates without having to cancel and start over.
67
* Built-in upgrade path to indexing agreements v2. So that indexers can be paid per byte instead of per entity.

packages/subgraph-service/contracts/DisputeManager.sol

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,19 @@ contract DisputeManager is
129129
return _createIndexingDisputeWithAllocation(msg.sender, disputeDeposit, allocationId, poi);
130130
}
131131

132+
/// @inheritdoc IDisputeManager
133+
function createIndexingFeeDispute(
134+
ISubgraphService.IndexingAgreementKey calldata agreementKey,
135+
bytes32 poi,
136+
uint256 entities
137+
) external override returns (bytes32) {
138+
// Get funds from fisherman
139+
_graphToken().pullTokens(msg.sender, disputeDeposit);
140+
141+
// Create a dispute
142+
return _createIndexingFeeDisputeWithAgreement(msg.sender, disputeDeposit, agreementKey, poi, entities);
143+
}
144+
132145
/// @inheritdoc IDisputeManager
133146
function createQueryDispute(bytes calldata attestationData) external override returns (bytes32) {
134147
// Get funds from fisherman
@@ -450,6 +463,75 @@ contract DisputeManager is
450463
return disputeId;
451464
}
452465

466+
/**
467+
* @notice Create indexing fee dispute internal function.
468+
* @param _fisherman The fisherman creating the dispute
469+
* @param _deposit Amount of tokens staked as deposit
470+
* @param _key The indexing agreement key
471+
* @param _poi The POI being disputed
472+
* @param _entities The number of entities disputed
473+
* @return The dispute id
474+
*/
475+
function _createIndexingFeeDisputeWithAgreement(
476+
address _fisherman,
477+
uint256 _deposit,
478+
ISubgraphService.IndexingAgreementKey calldata _key,
479+
bytes32 _poi,
480+
uint256 _entities
481+
) private returns (bytes32) {
482+
// Create a disputeId
483+
bytes32 disputeId = keccak256(
484+
abi.encodePacked(
485+
"IndexingFeeDisputeWithAgreement",
486+
_key.agreementId,
487+
_key.indexer,
488+
_key.payer,
489+
_poi,
490+
_entities
491+
)
492+
);
493+
494+
// Only one dispute at a time
495+
require(!isDisputeCreated(disputeId), DisputeManagerDisputeAlreadyCreated(disputeId));
496+
497+
// Agreement must have been collected on
498+
ISubgraphService.IndexingAgreementData memory agreement = _getSubgraphService().getIndexingAgreement(_key);
499+
require(agreement.lastCollectionAt > 0, DisputeManagerIndexingAgreementNotDisputable(_key));
500+
501+
// The indexer must be disputable
502+
IHorizonStaking.Provision memory provision = _graphStaking().getProvision(
503+
_key.indexer,
504+
address(_getSubgraphService())
505+
);
506+
require(provision.tokens != 0, DisputeManagerZeroTokens());
507+
508+
uint256 stakeSnapshot = _getStakeSnapshot(_key.indexer, provision.tokens);
509+
disputes[disputeId] = Dispute(
510+
_key.indexer,
511+
_fisherman,
512+
_deposit,
513+
0, // no related dispute,
514+
DisputeType.IndexingFeeDispute,
515+
IDisputeManager.DisputeStatus.Pending,
516+
block.timestamp,
517+
stakeSnapshot
518+
);
519+
520+
emit IndexingFeeDisputeCreated(
521+
disputeId,
522+
_key.indexer,
523+
_fisherman,
524+
_deposit,
525+
_key.payer,
526+
_key.agreementId,
527+
_poi,
528+
_entities,
529+
stakeSnapshot
530+
);
531+
532+
return disputeId;
533+
}
534+
453535
/**
454536
* @notice Accept a dispute
455537
* @param _disputeId The id of the dispute

packages/subgraph-service/contracts/SubgraphService.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,12 @@ contract SubgraphService is
675675
emit IndexingAgreementCanceled(indexer, payer, agreementId, payer);
676676
}
677677

678+
function getIndexingAgreement(
679+
IndexingAgreementKey memory key
680+
) external view returns (IndexingAgreementData memory) {
681+
return indexingAgreements[key.indexer][key.payer][key.agreementId];
682+
}
683+
678684
/**
679685
* @notice Decodes the indexing agreement metadata.
680686
*

packages/subgraph-service/contracts/interfaces/IDisputeManager.sol

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
pragma solidity 0.8.27;
44

55
import { Attestation } from "../libraries/Attestation.sol";
6+
import { ISubgraphService } from "./ISubgraphService.sol";
67

78
/**
89
* @title IDisputeManager
@@ -15,7 +16,8 @@ interface IDisputeManager {
1516
enum DisputeType {
1617
Null,
1718
IndexingDispute,
18-
QueryDispute
19+
QueryDispute,
20+
IndexingFeeDispute
1921
}
2022

2123
/// @notice Status of a dispute
@@ -108,6 +110,31 @@ interface IDisputeManager {
108110
uint256 stakeSnapshot
109111
);
110112

113+
/**
114+
* @dev Emitted when an indexing fee dispute is created for `agreementId` and `indexer`
115+
* by `fisherman`.
116+
* The event emits the amount of `tokens` deposited by the fisherman.
117+
* @param disputeId The dispute id
118+
* @param indexer The indexer address
119+
* @param fisherman The fisherman address
120+
* @param tokens The amount of tokens deposited by the fisherman
121+
* @param agreementId The agreement id
122+
* @param poi The POI disputed
123+
* @param entities The entities disputed
124+
* @param stakeSnapshot The stake snapshot of the indexer at the time of the dispute
125+
*/
126+
event IndexingFeeDisputeCreated(
127+
bytes32 indexed disputeId,
128+
address indexed indexer,
129+
address indexed fisherman,
130+
uint256 tokens,
131+
address payer,
132+
bytes16 agreementId,
133+
bytes32 poi,
134+
uint256 entities,
135+
uint256 stakeSnapshot
136+
);
137+
111138
/**
112139
* @dev Emitted when an indexing dispute is created for `allocationId` and `indexer`
113140
* by `fisherman`.
@@ -324,6 +351,11 @@ interface IDisputeManager {
324351
*/
325352
error DisputeManagerSubgraphServiceNotSet();
326353

354+
/**
355+
* @notice Thrown when the Indexing Agreement is not disputable
356+
*/
357+
error DisputeManagerIndexingAgreementNotDisputable(ISubgraphService.IndexingAgreementKey agreementKey);
358+
327359
/**
328360
* @notice Initialize this contract.
329361
* @param owner The owner of the contract
@@ -436,6 +468,27 @@ interface IDisputeManager {
436468
*/
437469
function createIndexingDispute(address allocationId, bytes32 poi) external returns (bytes32);
438470

471+
/**
472+
* @notice Create an indexing fee dispute for the arbitrator to resolve.
473+
* The disputes are created in reference to an indexing agreement and specifically
474+
* a POI and entities provided when collecting that agreement.
475+
* This function is called by a fisherman and it will pull `disputeDeposit` GRT tokens.
476+
*
477+
* Requirements:
478+
* - fisherman must have previously approved this contract to pull `disputeDeposit` amount
479+
* of tokens from their balance.
480+
*
481+
* @param agreementKey The indexing agreement key to dispute
482+
* @param poi The Proof of Indexing (POI) being disputed
483+
* @param entities The number of entities disputed
484+
* @return The dispute id
485+
*/
486+
function createIndexingFeeDispute(
487+
ISubgraphService.IndexingAgreementKey calldata agreementKey,
488+
bytes32 poi,
489+
uint256 entities
490+
) external returns (bytes32);
491+
439492
// -- Arbitrator --
440493

441494
/**

packages/subgraph-service/contracts/interfaces/ISubgraphService.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,4 +455,6 @@ interface ISubgraphService is IDataServiceFees {
455455
* @notice Cancel an indexing agreement by payer / signer.
456456
*/
457457
function cancelIndexingAgreementByPayer(address indexer, address payer, bytes16 agreementId) external;
458+
459+
function getIndexingAgreement(IndexingAgreementKey memory key) external view returns (IndexingAgreementData memory);
458460
}

packages/subgraph-service/test/subgraphService/indexing-agreement/integration.t.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ contract SubgraphServiceIndexingAgreementCancelTest is SubgraphServiceIndexingAg
4848
TestIndexerParams memory params = _setupIndexer(fuzzyParams, expectedTokensLocked);
4949

5050
uint256 signerPrivateKey = boundKey(unboundedSignerPrivateKey);
51+
vm.assume(fuzzyRCV.payer != address(0));
5152
_setupPayerWithEscrow(fuzzyRCV.payer, signerPrivateKey, params.indexer, expectedTotalTokensCollected);
5253

5354
uint256 agreementTokensPerSecond = 1;

0 commit comments

Comments
 (0)