Skip to content

feat: implement Indexing Agreements #1134

New issue

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

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

Already on GitHub? Sign in to your account

Draft
wants to merge 67 commits into
base: horizon
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
6f0bd5c
HACK: fix linter and compiler warnings
matiasedgeandnode May 23, 2025
58fd6f5
feat: implement Indexing Agreements
matiasedgeandnode Feb 13, 2025
4d4ac82
f: rename upgrade to update
matiasedgeandnode May 23, 2025
70f0f6d
f: inject currentEpoch
matiasedgeandnode May 23, 2025
54c2f42
f: rename _getAgreementStorage
matiasedgeandnode May 23, 2025
9793a0c
f: remove ternary
matiasedgeandnode May 23, 2025
6fac3b9
f: rename _encodeRCAU
matiasedgeandnode May 23, 2025
75242c4
f: remove comment
matiasedgeandnode May 26, 2025
053ff5c
f: use erc7201
matiasedgeandnode May 26, 2025
abf9558
f: cleanup modifiers
matiasedgeandnode May 26, 2025
5e52d5f
f: rename _hashRCA*712
matiasedgeandnode May 26, 2025
0956fe4
f: add to collect() NatSpec
matiasedgeandnode May 26, 2025
6d65a12
f: add blockNumber for disputes
matiasedgeandnode May 26, 2025
8838757
f: fix underflow
matiasedgeandnode May 26, 2025
e022525
f: add MIN_SECONDS_COLLECTION_WINDOW
matiasedgeandnode May 26, 2025
e95871d
f: linter
matiasedgeandnode May 30, 2025
bf58f96
HACK: make lib external
matiasedgeandnode May 23, 2025
50b1b3f
HACK: more external libs
matiasedgeandnode May 26, 2025
67fa601
HACK: DataServiceFeesLib
matiasedgeandnode May 26, 2025
325c827
HACK: ProvisionManager optimization - 25.595
matiasedgeandnode May 26, 2025
645d298
HACK: partial AllocationManager to lib - 24.520
matiasedgeandnode May 26, 2025
f336e4e
HACK: partial AllocationManager to lib - 22.521
matiasedgeandnode May 30, 2025
272d735
f: add update indexing agreement event
matiasedgeandnode Jun 3, 2025
2827f7f
f: TODO.md
matiasedgeandnode Jun 3, 2025
59735ef
f: add extended interface
matiasedgeandnode Jun 3, 2025
612dc73
f: remove comments
matiasedgeandnode Jun 3, 2025
4a72cdc
f: remove linter warnings
matiasedgeandnode Jun 3, 2025
5857ca4
f: minor cleanup
matiasedgeandnode Jun 4, 2025
876709b
f: more natSpec
matiasedgeandnode Jun 4, 2025
b94b013
f: all external ProvisionTracker
matiasedgeandnode Jun 4, 2025
73c0c4e
f: remove DataServiceFeesLib.Storage
matiasedgeandnode Jun 4, 2025
30133b6
f: correct params order
matiasedgeandnode Jun 4, 2025
2baed38
f: remove this. from ProvisionManager
matiasedgeandnode Jun 4, 2025
ab30731
f: revert LinkedList to internal
matiasedgeandnode Jun 4, 2025
bd3495e
f: remove this. from SubgraphService
matiasedgeandnode Jun 4, 2025
b359808
f: remove _verifyAllocationProof duplicated
matiasedgeandnode Jun 4, 2025
bbf2b09
f: lint warnings
matiasedgeandnode Jun 4, 2025
aaec2e0
f: removed EIP712_ALLOCATION_ID_PROOF_TYPEHASH
matiasedgeandnode Jun 4, 2025
ebb38aa
HACK: final AllocationManager to lib - 20.785
matiasedgeandnode Jun 4, 2025
fb2183d
HACK: remove extension - 22.884
matiasedgeandnode Jun 4, 2025
31d1c47
f: update todo md
matiasedgeandnode Jun 4, 2025
b767d80
f: revert ProvisionTRacker
matiasedgeandnode Jun 4, 2025
4c5573f
f: remove unused extension references
matiasedgeandnode Jun 5, 2025
7dee19f
f: rename Manager to StorageManager
matiasedgeandnode Jun 5, 2025
21961c4
f: add to IndexingAgreementWrongDataService
matiasedgeandnode Jun 5, 2025
03ed657
f: comment one agreement per alloc
matiasedgeandnode Jun 5, 2025
19a4270
f: remove RecurringCollectorAgreementInvalidParameters
matiasedgeandnode Jun 5, 2025
003dd61
f: validate before setting in storage
matiasedgeandnode Jun 5, 2025
bd49826
f: natspec
matiasedgeandnode Jun 5, 2025
b900097
f: add now to RecurringCollectorAgreementDeadlineElapsed
matiasedgeandnode Jun 5, 2025
1857e15
f: natspec
matiasedgeandnode Jun 5, 2025
eb81d79
f: add CancelAgreementBy.ThirdParty
matiasedgeandnode Jun 6, 2025
415bc5b
f: reword and document onCloseAllocation
matiasedgeandnode Jun 6, 2025
56c86a7
f: add natspec explaining the stake lock mechanism
matiasedgeandnode Jun 6, 2025
93f7342
f: add natspec from smells
matiasedgeandnode Jun 6, 2025
8e65d22
f: fully commit to DataServiceFeesLib
matiasedgeandnode Jun 6, 2025
948d257
f: remove unused getGraphStaking()
matiasedgeandnode Jun 6, 2025
f0d158f
f: more NatSpec
matiasedgeandnode Jun 6, 2025
c984e24
f: more NatSpec
matiasedgeandnode Jun 6, 2025
f6fe0cd
f: more NatSpec
matiasedgeandnode Jun 6, 2025
2e881b5
f: more NatSpec
matiasedgeandnode Jun 6, 2025
8f8a0d2
f: more NatSpec
matiasedgeandnode Jun 6, 2025
7ca134d
f: more NatSpec
matiasedgeandnode Jun 6, 2025
6c373c8
f: more NatSpec
matiasedgeandnode Jun 6, 2025
84a5edc
f: more NatSpec
matiasedgeandnode Jun 6, 2025
bfe97fe
f: more NatSpec
matiasedgeandnode Jun 6, 2025
f33e23d
f: more NatSpec
matiasedgeandnode Jun 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions IndexingPaymentsTodo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Still pending

* `require(provision.tokens != 0, DisputeManagerZeroTokens());` - Document or fix?
* Check code coverage
* Don't love cancel agreement on stop service / close stale allocation.
* Arbitration Charter: Update to support disputing IndexingFee.

# Done

* DONE: ~~* Remove extension if I can fit everything in one service?~~
* DONE: ~~* One Interface for all subgraph~~
* DONE: ~~* Missing Upgrade event for subgraph service~~
* DONE: ~~* Check contract size~~
* DONE: ~~Switch cancel event in recurring collector to use Enum~~
* DONE: ~~Switch timestamps to uint64~~
* DONE: ~~Check that UUID-v4 fits in `bytes16`~~
* DONE: ~~Double check cancelation policy. Who can cancel when? Right now is either party at any time. Answer: If gateway cancels allow collection till that point.~~
* DONE: ~~If an indexer closes an allocation, what should happen to the accepeted agreement? Answer: Look into canceling agreement as part of stop service.~~
* DONE: ~~Switch `duration` for `endsAt`? Answer: Do it.~~
* DONE: ~~Support a way for gateway to shop an agreement around? Deadline + dedup key? So only one agreement with the dedupe key can be accepted? Answer: No. Agreements will be "signaled" as approved or rejected on the API call that sends the agreement. We'll trust (and verify) that that's the case.~~
* DONE: ~~Test `upgrade` paths~~
* DONE: ~~Fix upgrade.t.sol, lots of comments~~
* DONE: ~~How do we solve for the case where an indexer has reached their max expected payout for the initial sync but haven't reached the current epoch (thus their POI is incorrect)? Answer: Signal in the event that the max amount was collected, so that fisherman understand the case.~~
* DONE: ~~Debate epoch check protocol team. Maybe don't revert but store it in event. Pablo suggest block number instead of epoch.~~
* DONE: ~~Should we set a different param for initial collection time max? Some subgraphs take a lot to catch up. Answer: Do nothing. Make sure that zero POIs allow to eventually sync~~
* DONE: ~~Since an allocation is required for collecting, do we want to expect that the allocation is not stale? Do we want to add code to collect rewards as part of the collection of fees? Make sure allocation is more than one epoch old if we attempt this. Answer: Ignore stale allocation~~
* DONE: ~~If service wants to collect more than collector allows. Collector limits but doesn't tell the service? Currently reverts. Answer: Allow for max allowed~~
* DONE: ~~What should happen if the escrow doesn't have enough funds? Answer: Reverts~~
* DONE: ~~Don't pay for entities on initial collection? Where did we land in terms of payment terms? Answer: pay initial~~
* DONE: ~~Test lock stake~~
* DONE: ~~Reduce the number of errors declared and returned~~
* DONE: ~~Support `DisputeManager`~~
* DONE: ~~Check upgrade conditions. Support indexing agreement upgradability, so that there is a mechanism to adjust the rates without having to cancel and start over.~~
* DONE: ~~Maybe check that the epoch the indexer is sending is the one the transaction will be run in?~~
* DONE: ~~Should we deal with zero entities declared as a special case?~~
* DONE: ~~Support for agreements that end up in `RecurringCollectorCollectionTooLate` or ways to avoid getting to that state.~~
* DONE: ~~Make `agreementId` unique globally so that we don't need the full tuple (`payer`+`indexer`+`agreementId`) as key?~~
* DONE: ~~Maybe IRecurringCollector.cancel(address payer, address serviceProvider, bytes16 agreementId) should only take in agreementId?~~
* DONE: ~~Unify to one error in Decoder.sol~~
* DONE: ~~Built-in upgrade path to indexing agreements v2~~
* DONE: ~~Missing events for accept, cancel, upgrade RCAs.~~

# Won't Fix

* Add upgrade path to v2 collector terms
* Expose a function that indexers can use to calculate the tokens to be collected and other collection params?
* Place all agreement terms into one struct
* It's more like a collect + cancel since the indexer is expected to stop work then and there. When posting a POI that's < N-1 epoch. Answer: Emit signal that the collection is meant to be final. Counter: Won't do since collector can't signal back to data service that payment is maxed out. Could emit an event from the collector, but is it really worth it? Right now any collection where epoch POI < current POI is suspect.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { IDataServiceFees } from "../interfaces/IDataServiceFees.sol";

import { ProvisionTracker } from "../libraries/ProvisionTracker.sol";
import { LinkedList } from "../../libraries/LinkedList.sol";
import { DataServiceFeesLib } from "../libraries/DataServiceFeesLib.sol";

import { DataService } from "../DataService.sol";
import { DataServiceFeesV1Storage } from "./DataServiceFeesStorage.sol";
Expand Down Expand Up @@ -41,23 +42,16 @@ abstract contract DataServiceFees is DataService, DataServiceFeesV1Storage, IDat
* @param _unlockTimestamp The timestamp when the tokens can be released
*/
function _lockStake(address _serviceProvider, uint256 _tokens, uint256 _unlockTimestamp) internal {
require(_tokens != 0, DataServiceFeesZeroTokens());
feesProvisionTracker.lock(_graphStaking(), _serviceProvider, _tokens, _delegationRatio);

LinkedList.List storage claimsList = claimsLists[_serviceProvider];

// Save item and add to list
bytes32 claimId = _buildStakeClaimId(_serviceProvider, claimsList.nonce);
claims[claimId] = StakeClaim({
tokens: _tokens,
createdAt: block.timestamp,
releasableAt: _unlockTimestamp,
nextClaim: bytes32(0)
});
if (claimsList.count != 0) claims[claimsList.tail].nextClaim = claimId;
claimsList.addTail(claimId);

emit StakeClaimLocked(_serviceProvider, claimId, _tokens, _unlockTimestamp);
DataServiceFeesLib.lockStake(
feesProvisionTracker,
claims,
claimsLists,
_graphStaking(),
_delegationRatio,
_serviceProvider,
_tokens,
_unlockTimestamp
);
}

/**
Expand Down Expand Up @@ -92,23 +86,7 @@ abstract contract DataServiceFees is DataService, DataServiceFeesV1Storage, IDat
* @return The updated accumulator data
*/
function _processStakeClaim(bytes32 _claimId, bytes memory _acc) private returns (bool, bytes memory) {
StakeClaim memory claim = _getStakeClaim(_claimId);

// early exit
if (claim.releasableAt > block.timestamp) {
return (true, LinkedList.NULL_BYTES);
}

// decode
(uint256 tokensClaimed, address serviceProvider) = abi.decode(_acc, (uint256, address));

// process
feesProvisionTracker.release(serviceProvider, claim.tokens);
emit StakeClaimReleased(serviceProvider, _claimId, claim.tokens, claim.releasableAt);

// encode
_acc = abi.encode(tokensClaimed + claim.tokens, serviceProvider);
return (false, _acc);
return DataServiceFeesLib.processStakeClaim(feesProvisionTracker, claims, _claimId, _acc);
}

/**
Expand All @@ -117,18 +95,7 @@ abstract contract DataServiceFees is DataService, DataServiceFeesV1Storage, IDat
* @param _claimId The ID of the stake claim to delete
*/
function _deleteStakeClaim(bytes32 _claimId) private {
delete claims[_claimId];
}

/**
* @notice Gets the details of a stake claim
* @param _claimId The ID of the stake claim
* @return The stake claim details
*/
function _getStakeClaim(bytes32 _claimId) private view returns (StakeClaim memory) {
StakeClaim memory claim = claims[_claimId];
require(claim.createdAt != 0, DataServiceFeesClaimNotFound(_claimId));
return claim;
DataServiceFeesLib.deleteStakeClaim(claims, _claimId);
}

/**
Expand All @@ -138,16 +105,6 @@ abstract contract DataServiceFees is DataService, DataServiceFeesV1Storage, IDat
* @return The next stake claim ID
*/
function _getNextStakeClaim(bytes32 _claimId) private view returns (bytes32) {
return claims[_claimId].nextClaim;
}

/**
* @notice Builds a stake claim ID
* @param _serviceProvider The address of the service provider
* @param _nonce A nonce of the stake claim
* @return The stake claim ID
*/
function _buildStakeClaimId(address _serviceProvider, uint256 _nonce) private view returns (bytes32) {
return keccak256(abi.encodePacked(address(this), _serviceProvider, _nonce));
return DataServiceFeesLib.getNextStakeClaim(claims, _claimId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;

import { ProvisionTracker } from "./ProvisionTracker.sol";
import { IDataServiceFees } from "../interfaces/IDataServiceFees.sol";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would remove the dependency with IDataServiceFees any event or error that we use from there I think should probably instead live in this lib contract. Note that if we move any errors into this file we would need to rename their prefix IDataServiceFees.DataServiceFeesClaimNotFound -> StakeClaimsClaimNotFound

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also the struct?

struct StakeClaim {
        uint256 tokens;
        uint256 createdAt;
        uint256 releasableAt;
        bytes32 nextClaim;
    }

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yes that makes sense. to avoid name collision we can rename the struct to State similar to what we have with Allocation.State and a few others.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

look at this last, maybe drop if not enough time or will power.

import { IHorizonStaking } from "../../interfaces/IHorizonStaking.sol";
import { LinkedList } from "../../libraries/LinkedList.sol";

library DataServiceFeesLib {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
library DataServiceFeesLib {
library StakeClaims {

How about we rename this? I'm personally not very fond of the Lib naming approach, and I feel we can be more descriptive with the name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, it was meant to be a placeholder until we agreed on the validity of the approach.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do this.

using ProvisionTracker for mapping(address => uint256);
using LinkedList for LinkedList.List;

/**
* @notice Locks stake for a service provider to back a payment.
* Creates a stake claim, which is stored in a linked list by service provider.
* @dev Requirements:
* - The associated provision must have enough available tokens to lock the stake.
*
* Emits a {StakeClaimLocked} event.
*
* @param feesProvisionTracker The mapping that tracks the provision tokens for each service provider
* @param claims The mapping that stores stake claims by their ID
* @param claimsLists The mapping that stores linked lists of stake claims by service provider
* @param graphStaking The Horizon staking contract used to lock the tokens
* @param _delegationRatio The delegation ratio to use for the stake claim
* @param _serviceProvider The address of the service provider
* @param _tokens The amount of tokens to lock in the claim
* @param _unlockTimestamp The timestamp when the tokens can be released
*/
function lockStake(
mapping(address => uint256) storage feesProvisionTracker,
mapping(bytes32 => IDataServiceFees.StakeClaim) storage claims,
mapping(address serviceProvider => LinkedList.List list) storage claimsLists,
IHorizonStaking graphStaking,
uint32 _delegationRatio,
address _serviceProvider,
uint256 _tokens,
uint256 _unlockTimestamp
) external {
require(_tokens != 0, IDataServiceFees.DataServiceFeesZeroTokens());
feesProvisionTracker.lock(graphStaking, _serviceProvider, _tokens, _delegationRatio);

LinkedList.List storage claimsList = claimsLists[_serviceProvider];

// Save item and add to list
bytes32 claimId = _buildStakeClaimId(_serviceProvider, claimsList.nonce);
claims[claimId] = IDataServiceFees.StakeClaim({
tokens: _tokens,
createdAt: block.timestamp,
releasableAt: _unlockTimestamp,
nextClaim: bytes32(0)
});
if (claimsList.count != 0) claims[claimsList.tail].nextClaim = claimId;
claimsList.addTail(claimId);

emit IDataServiceFees.StakeClaimLocked(_serviceProvider, claimId, _tokens, _unlockTimestamp);
}

/**
* @notice Processes a stake claim, releasing the tokens if the claim has expired.
* @dev This function is used as a callback in the stake claims linked list traversal.
* @param feesProvisionTracker The mapping that tracks the provision tokens for each service provider.
* @param claims The mapping that stores stake claims by their ID.
* @param _claimId The ID of the stake claim to process.
* @param _acc The accumulator data, which contains the total tokens claimed and the service provider address.
* @return Whether the stake claim is still locked, indicating that the traversal should continue or stop.
* @return The updated accumulator data
*/
function processStakeClaim(
mapping(address serviceProvider => uint256 tokens) storage feesProvisionTracker,
mapping(bytes32 claimId => IDataServiceFees.StakeClaim claim) storage claims,
bytes32 _claimId,
bytes memory _acc
) external returns (bool, bytes memory) {
IDataServiceFees.StakeClaim memory claim = claims[_claimId];
require(claim.createdAt != 0, IDataServiceFees.DataServiceFeesClaimNotFound(_claimId));

// early exit
if (claim.releasableAt > block.timestamp) {
return (true, LinkedList.NULL_BYTES);
}

// decode
(uint256 tokensClaimed, address serviceProvider) = abi.decode(_acc, (uint256, address));

// process
feesProvisionTracker.release(serviceProvider, claim.tokens);
emit IDataServiceFees.StakeClaimReleased(serviceProvider, _claimId, claim.tokens, claim.releasableAt);

// encode
_acc = abi.encode(tokensClaimed + claim.tokens, serviceProvider);
return (false, _acc);
}

/**
* @notice Deletes a stake claim.
* @dev This function is used as a callback in the stake claims linked list traversal.
* @param claims The mapping that stores stake claims by their ID
* @param claimId The ID of the stake claim to delete
*/
function deleteStakeClaim(
mapping(bytes32 claimId => IDataServiceFees.StakeClaim claim) storage claims,
bytes32 claimId
) external {
delete claims[claimId];
}

/**
* @notice Gets the next stake claim in the linked list
* @dev This function is used as a callback in the stake claims linked list traversal.
* @param claims The mapping that stores stake claims by their ID
* @param claimId The ID of the stake claim
* @return The next stake claim ID
*/
function getNextStakeClaim(
mapping(bytes32 claimId => IDataServiceFees.StakeClaim claim) storage claims,
bytes32 claimId
) external view returns (bytes32) {
return claims[claimId].nextClaim;
}

/**
* @notice Builds a stake claim ID
* @param serviceProvider The address of the service provider
* @param nonce A nonce of the stake claim
* @return The stake claim ID
*/
function _buildStakeClaimId(address serviceProvider, uint256 nonce) internal view returns (bytes32) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is now a linked lib I think it could be useful to expose this internal function with an external getter as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do this.

return keccak256(abi.encodePacked(address(this), serviceProvider, nonce));
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes to this file can now probably be reverted since we are at <23kb

Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,7 @@ abstract contract ProvisionManager is Initializable, GraphDirectory, ProvisionMa
* @param serviceProvider The address of the service provider.
*/
modifier onlyValidProvision(address serviceProvider) virtual {
IHorizonStaking.Provision memory provision = _getProvision(serviceProvider);
_checkProvisionTokens(provision);
_checkProvisionParameters(provision, false);
_requireValidProvision(serviceProvider);
_;
}

Expand Down Expand Up @@ -176,7 +174,7 @@ abstract contract ProvisionManager is Initializable, GraphDirectory, ProvisionMa
* @param _max The maximum allowed value for the provision tokens.
*/
function _setProvisionTokensRange(uint256 _min, uint256 _max) internal {
require(_min <= _max, ProvisionManagerInvalidRange(_min, _max));
_requireLTE(_min, _max);
_minimumProvisionTokens = _min;
_maximumProvisionTokens = _max;
emit ProvisionTokensRangeSet(_min, _max);
Expand All @@ -188,7 +186,7 @@ abstract contract ProvisionManager is Initializable, GraphDirectory, ProvisionMa
* @param _max The maximum allowed value for the max verifier cut.
*/
function _setVerifierCutRange(uint32 _min, uint32 _max) internal {
require(_min <= _max, ProvisionManagerInvalidRange(_min, _max));
_requireLTE(_min, _max);
require(PPMMath.isValidPPM(_max), ProvisionManagerInvalidRange(_min, _max));
_minimumVerifierCut = _min;
_maximumVerifierCut = _max;
Expand All @@ -201,21 +199,31 @@ abstract contract ProvisionManager is Initializable, GraphDirectory, ProvisionMa
* @param _max The maximum allowed value for the thawing period.
*/
function _setThawingPeriodRange(uint64 _min, uint64 _max) internal {
require(_min <= _max, ProvisionManagerInvalidRange(_min, _max));
_requireLTE(_min, _max);
_minimumThawingPeriod = _min;
_maximumThawingPeriod = _max;
emit ThawingPeriodRangeSet(_min, _max);
}

/**
* @notice Checks if a provision of a service provider is valid according
* to the parameter ranges established.
* @param _serviceProvider The address of the service provider.
*/
function _requireValidProvision(address _serviceProvider) internal view {
IHorizonStaking.Provision memory provision = _getProvision(_serviceProvider);
_checkProvisionTokens(provision);
_checkProvisionParameters(provision, false);
}

// -- checks --

/**
* @notice Checks if the provision tokens of a service provider are within the valid range.
* @param _serviceProvider The address of the service provider.
*/
function _checkProvisionTokens(address _serviceProvider) internal view virtual {
IHorizonStaking.Provision memory provision = _getProvision(_serviceProvider);
_checkProvisionTokens(provision);
_checkProvisionTokens(_getProvision(_serviceProvider));
}

/**
Expand All @@ -238,8 +246,7 @@ abstract contract ProvisionManager is Initializable, GraphDirectory, ProvisionMa
* @param _checkPending If true, checks the pending provision parameters.
*/
function _checkProvisionParameters(address _serviceProvider, bool _checkPending) internal view virtual {
IHorizonStaking.Provision memory provision = _getProvision(_serviceProvider);
_checkProvisionParameters(provision, _checkPending);
_checkProvisionParameters(_getProvision(_serviceProvider), _checkPending);
}

/**
Expand Down Expand Up @@ -320,4 +327,13 @@ abstract contract ProvisionManager is Initializable, GraphDirectory, ProvisionMa
function _checkValueInRange(uint256 _value, uint256 _min, uint256 _max, bytes memory _revertMessage) private pure {
require(_value.isInRange(_min, _max), ProvisionManagerInvalidValue(_revertMessage, _value, _min, _max));
}

/**
* @notice Requires that a value is less than or equal to another value.
* @param _a The value to check.
* @param _b The value to compare against.
*/
function _requireLTE(uint256 _a, uint256 _b) private pure {
require(_a <= _b, ProvisionManagerInvalidRange(_a, _b));
}
}
Loading