diff --git a/packages/horizon/contracts/interfaces/internal/IHorizonStakingMain.sol b/packages/horizon/contracts/interfaces/internal/IHorizonStakingMain.sol index 25c5f85aa..e296f88e5 100644 --- a/packages/horizon/contracts/interfaces/internal/IHorizonStakingMain.sol +++ b/packages/horizon/contracts/interfaces/internal/IHorizonStakingMain.sol @@ -352,6 +352,11 @@ interface IHorizonStakingMain { */ error HorizonStakingInvalidThawingPeriod(uint64 thawingPeriod, uint64 maxThawingPeriod); + /** + * @notice Thrown when attempting to create a provision for a data service that already has a provision. + */ + error HorizonStakingProvisionAlreadyExists(); + // -- Errors: stake -- /** diff --git a/packages/horizon/contracts/staking/HorizonStaking.sol b/packages/horizon/contracts/staking/HorizonStaking.sol index 4d3349b7a..45ce7c8cb 100644 --- a/packages/horizon/contracts/staking/HorizonStaking.sol +++ b/packages/horizon/contracts/staking/HorizonStaking.sol @@ -606,6 +606,7 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain { _thawingPeriod <= _maxThawingPeriod, HorizonStakingInvalidThawingPeriod(_thawingPeriod, _maxThawingPeriod) ); + require(_provisions[_serviceProvider][_verifier].createdAt == 0, HorizonStakingProvisionAlreadyExists()); uint256 tokensIdle = _getIdleStake(_serviceProvider); require(_tokens <= tokensIdle, HorizonStakingInsufficientIdleStake(_tokens, tokensIdle)); diff --git a/packages/horizon/test/staking/provision/provision.t.sol b/packages/horizon/test/staking/provision/provision.t.sol index fc941ec7f..3394460fb 100644 --- a/packages/horizon/test/staking/provision/provision.t.sol +++ b/packages/horizon/test/staking/provision/provision.t.sol @@ -6,7 +6,6 @@ import "forge-std/Test.sol"; import { HorizonStakingTest } from "../HorizonStaking.t.sol"; contract HorizonStakingProvisionTest is HorizonStakingTest { - /* * TESTS */ @@ -68,6 +67,21 @@ contract HorizonStakingProvisionTest is HorizonStakingTest { staking.provision(users.indexer, subgraphDataServiceAddress, provisionTokens, 0, 0); } + function testProvision_RevertWhen_AlreadyExists( + uint256 amount, + uint32 maxVerifierCut, + uint64 thawingPeriod + ) public useIndexer useProvision(amount / 2, maxVerifierCut, thawingPeriod) { + resetPrank(users.indexer); + + token.approve(address(staking), amount / 2); + staking.stake(amount / 2); + + bytes memory expectedError = abi.encodeWithSignature("HorizonStakingProvisionAlreadyExists()"); + vm.expectRevert(expectedError); + staking.provision(users.indexer, subgraphDataServiceAddress, amount / 2, maxVerifierCut, thawingPeriod); + } + function testProvision_OperatorAddTokensToProvision( uint256 amount, uint32 maxVerifierCut, @@ -94,7 +108,7 @@ contract HorizonStakingProvisionTest is HorizonStakingTest { ) public useIndexer useProvision(amount, maxVerifierCut, thawingPeriod) { vm.startPrank(users.operator); bytes memory expectedError = abi.encodeWithSignature( - "HorizonStakingNotAuthorized(address,address,address)", + "HorizonStakingNotAuthorized(address,address,address)", users.operator, users.indexer, subgraphDataServiceAddress @@ -102,4 +116,4 @@ contract HorizonStakingProvisionTest is HorizonStakingTest { vm.expectRevert(expectedError); staking.provision(users.indexer, subgraphDataServiceAddress, amount, maxVerifierCut, thawingPeriod); } -} \ No newline at end of file +}