diff --git a/packages/subgraph-service/contracts/DisputeManager.sol b/packages/subgraph-service/contracts/DisputeManager.sol index 5ad0d8d56..f234057aa 100644 --- a/packages/subgraph-service/contracts/DisputeManager.sol +++ b/packages/subgraph-service/contracts/DisputeManager.sol @@ -52,6 +52,11 @@ contract DisputeManager is using TokenUtils for IGraphToken; using PPMMath for uint256; + // -- Constants -- + + // Maximum value for fisherman reward cut in PPM + uint32 public constant MAX_FISHERMAN_REWARD_CUT = 500000; + // -- Modifiers -- /** @@ -659,8 +664,7 @@ contract DisputeManager is * @param _fishermanRewardCut Reward as a percentage of indexer stake */ function _setFishermanRewardCut(uint32 _fishermanRewardCut) private { - // Must be within 0% to 100% (inclusive) - require(PPMMath.isValidPPM(_fishermanRewardCut), DisputeManagerInvalidFishermanReward(_fishermanRewardCut)); + require(_fishermanRewardCut <= MAX_FISHERMAN_REWARD_CUT, DisputeManagerInvalidFishermanReward(_fishermanRewardCut)); fishermanRewardCut = _fishermanRewardCut; emit FishermanRewardCutSet(fishermanRewardCut); } diff --git a/packages/subgraph-service/test/SubgraphBaseTest.t.sol b/packages/subgraph-service/test/SubgraphBaseTest.t.sol index a381d1695..9991ab8a5 100644 --- a/packages/subgraph-service/test/SubgraphBaseTest.t.sol +++ b/packages/subgraph-service/test/SubgraphBaseTest.t.sol @@ -142,6 +142,7 @@ abstract contract SubgraphBaseTest is Utils, Constants { ) ); disputeManager = DisputeManager(disputeManagerProxy); + disputeManager.transferOwnership(users.governor); tapCollector = new TAPCollector("TAPCollector", "1", address(controller)); address subgraphServiceImplementation = address( @@ -154,8 +155,6 @@ abstract contract SubgraphBaseTest is Utils, Constants { ); subgraphService = SubgraphService(subgraphServiceProxy); - disputeManager.setSubgraphService(address(subgraphService)); - stakingExtension = new HorizonStakingExtension( address(controller), address(subgraphService) @@ -177,6 +176,7 @@ abstract contract SubgraphBaseTest is Utils, Constants { ); resetPrank(users.governor); + disputeManager.setSubgraphService(address(subgraphService)); proxyAdmin.upgrade(stakingProxy, address(stakingBase)); proxyAdmin.acceptProxy(stakingBase, stakingProxy); staking = IHorizonStaking(address(stakingProxy)); diff --git a/packages/subgraph-service/test/disputes/DisputeManager.t.sol b/packages/subgraph-service/test/disputeManager/DisputeManager.t.sol similarity index 97% rename from packages/subgraph-service/test/disputes/DisputeManager.t.sol rename to packages/subgraph-service/test/disputeManager/DisputeManager.t.sol index 954cef783..9f80512b4 100644 --- a/packages/subgraph-service/test/disputes/DisputeManager.t.sol +++ b/packages/subgraph-service/test/disputeManager/DisputeManager.t.sol @@ -18,6 +18,12 @@ contract DisputeManagerTest is SubgraphServiceSharedTest { * MODIFIERS */ + modifier useGovernor() { + vm.startPrank(users.governor); + _; + vm.stopPrank(); + } + modifier useFisherman { vm.startPrank(users.fisherman); _; diff --git a/packages/subgraph-service/test/disputeManager/constructor/constructor.t.sol b/packages/subgraph-service/test/disputeManager/constructor/constructor.t.sol new file mode 100644 index 000000000..d4f100f68 --- /dev/null +++ b/packages/subgraph-service/test/disputeManager/constructor/constructor.t.sol @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import "forge-std/Test.sol"; + +import { PPMMath } from "@graphprotocol/horizon/contracts/libraries/PPMMath.sol"; +import { GraphDirectory } from "@graphprotocol/horizon/contracts/utilities/GraphDirectory.sol"; +import { UnsafeUpgrades } from "openzeppelin-foundry-upgrades/Upgrades.sol"; +import { DisputeManager } from "../../../contracts/DisputeManager.sol"; +import { DisputeManagerTest } from "../DisputeManager.t.sol"; +import { IDisputeManager } from "../../../contracts/interfaces/IDisputeManager.sol"; + +contract DisputeManagerConstructorTest is DisputeManagerTest { + using PPMMath for uint256; + + /* + * MODIFIERS + */ + + modifier useDeployer() { + vm.startPrank(users.deployer); + _; + vm.stopPrank(); + } + + /* + * HELPERS + */ + + function _initializeDisputeManager( + address implementation, + address arbitrator, + uint64 disputePeriod, + uint256 disputeDeposit, + uint32 fishermanRewardPercentage, + uint32 maxSlashingPercentage + ) private returns (address) { + return UnsafeUpgrades.deployTransparentProxy( + implementation, + users.governor, + abi.encodeCall( + DisputeManager.initialize, + (arbitrator, disputePeriod, disputeDeposit, fishermanRewardPercentage, maxSlashingPercentage) + ) + ); + } + + /* + * TESTS + */ + + function test_Constructor( + uint32 fishermanRewardPercentage + ) public useDeployer { + vm.assume(fishermanRewardPercentage <= disputeManager.MAX_FISHERMAN_REWARD_CUT()); + address disputeManagerImplementation = address(new DisputeManager(address(controller))); + address proxy = _initializeDisputeManager( + disputeManagerImplementation, + users.arbitrator, + disputePeriod, + disputeDeposit, + fishermanRewardPercentage, + maxSlashingPercentage + ); + + DisputeManager disputeManager = DisputeManager(proxy); + assertEq(disputeManager.arbitrator(), users.arbitrator); + assertEq(disputeManager.disputePeriod(), disputePeriod); + assertEq(disputeManager.disputeDeposit(), disputeDeposit); + assertEq(disputeManager.fishermanRewardCut(), fishermanRewardPercentage); + } + + function test_Constructor_RevertIf_ControllerAddressIsZero() public useDeployer { + bytes memory expectedError = abi.encodeWithSelector( + GraphDirectory.GraphDirectoryInvalidZeroAddress.selector, + "Controller" + ); + vm.expectRevert(expectedError); + new DisputeManager(address(0)); + } + + function test_Constructor_RevertIf_ArbitratorAddressIsZero() public useDeployer { + address disputeManagerImplementation = address(new DisputeManager(address(controller))); + bytes memory expectedError = abi.encodeWithSelector( + IDisputeManager.DisputeManagerInvalidZeroAddress.selector + ); + vm.expectRevert(expectedError); + _initializeDisputeManager( + disputeManagerImplementation, + address(0), + disputePeriod, + disputeDeposit, + fishermanRewardPercentage, + maxSlashingPercentage + ); + } + + function test_Constructor_RevertIf_InvalidDisputePeriod() public useDeployer { + address disputeManagerImplementation = address(new DisputeManager(address(controller))); + bytes memory expectedError = abi.encodeWithSelector( + IDisputeManager.DisputeManagerDisputePeriodZero.selector + ); + vm.expectRevert(expectedError); + _initializeDisputeManager( + disputeManagerImplementation, + users.arbitrator, + 0, + disputeDeposit, + fishermanRewardPercentage, + maxSlashingPercentage + ); + } + + function test_Constructor_RevertIf_InvalidDisputeDeposit() public useDeployer { + address disputeManagerImplementation = address(new DisputeManager(address(controller))); + bytes memory expectedError = abi.encodeWithSelector( + IDisputeManager.DisputeManagerInvalidDisputeDeposit.selector, + 0 + ); + vm.expectRevert(expectedError); + _initializeDisputeManager( + disputeManagerImplementation, + users.arbitrator, + disputePeriod, + 0, + fishermanRewardPercentage, + maxSlashingPercentage + ); + } + + function test_Constructor_RevertIf_InvalidFishermanRewardPercentage(uint32 _fishermanRewardPercentage) public useDeployer { + vm.assume(_fishermanRewardPercentage > disputeManager.MAX_FISHERMAN_REWARD_CUT()); + address disputeManagerImplementation = address(new DisputeManager(address(controller))); + bytes memory expectedError = abi.encodeWithSelector( + IDisputeManager.DisputeManagerInvalidFishermanReward.selector, + _fishermanRewardPercentage + ); + vm.expectRevert(expectedError); + _initializeDisputeManager( + disputeManagerImplementation, + users.arbitrator, + disputePeriod, + disputeDeposit, + _fishermanRewardPercentage, + maxSlashingPercentage + ); + } + + function test_Constructor_RevertIf_InvalidMaxSlashingPercentage(uint32 _maxSlashingPercentage) public useDeployer { + vm.assume(_maxSlashingPercentage > PPMMath.MAX_PPM); + address disputeManagerImplementation = address(new DisputeManager(address(controller))); + bytes memory expectedError = abi.encodeWithSelector( + IDisputeManager.DisputeManagerInvalidMaxSlashingCut.selector, + _maxSlashingPercentage + ); + vm.expectRevert(expectedError); + _initializeDisputeManager( + disputeManagerImplementation, + users.arbitrator, + disputePeriod, + disputeDeposit, + fishermanRewardPercentage, + _maxSlashingPercentage + ); + } +} \ No newline at end of file diff --git a/packages/subgraph-service/test/disputes/accept.t.sol b/packages/subgraph-service/test/disputeManager/disputes/accept.t.sol similarity index 98% rename from packages/subgraph-service/test/disputes/accept.t.sol rename to packages/subgraph-service/test/disputeManager/disputes/accept.t.sol index 8e7a7f593..29e3bb6f5 100644 --- a/packages/subgraph-service/test/disputes/accept.t.sol +++ b/packages/subgraph-service/test/disputeManager/disputes/accept.t.sol @@ -4,8 +4,8 @@ pragma solidity 0.8.26; import "forge-std/Test.sol"; import { PPMMath } from "@graphprotocol/horizon/contracts/libraries/PPMMath.sol"; -import { IDisputeManager } from "../../contracts/interfaces/IDisputeManager.sol"; -import { DisputeManagerTest } from "./DisputeManager.t.sol"; +import { IDisputeManager } from "../../../contracts/interfaces/IDisputeManager.sol"; +import { DisputeManagerTest } from "../DisputeManager.t.sol"; import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; contract DisputeManagerAcceptDisputeTest is DisputeManagerTest { diff --git a/packages/subgraph-service/test/disputes/cancel.t.sol b/packages/subgraph-service/test/disputeManager/disputes/cancel.t.sol similarity index 94% rename from packages/subgraph-service/test/disputes/cancel.t.sol rename to packages/subgraph-service/test/disputeManager/disputes/cancel.t.sol index 4affbd6f4..abd55e051 100644 --- a/packages/subgraph-service/test/disputes/cancel.t.sol +++ b/packages/subgraph-service/test/disputeManager/disputes/cancel.t.sol @@ -3,8 +3,8 @@ pragma solidity 0.8.26; import "forge-std/Test.sol"; -import { IDisputeManager } from "../../contracts/interfaces/IDisputeManager.sol"; -import { DisputeManagerTest } from "./DisputeManager.t.sol"; +import { IDisputeManager } from "../../../contracts/interfaces/IDisputeManager.sol"; +import { DisputeManagerTest } from "../DisputeManager.t.sol"; contract DisputeManagerCancelDisputeTest is DisputeManagerTest { diff --git a/packages/subgraph-service/test/disputes/create.t.sol b/packages/subgraph-service/test/disputeManager/disputes/create.t.sol similarity index 97% rename from packages/subgraph-service/test/disputes/create.t.sol rename to packages/subgraph-service/test/disputeManager/disputes/create.t.sol index eb1907ebe..05147ec73 100644 --- a/packages/subgraph-service/test/disputes/create.t.sol +++ b/packages/subgraph-service/test/disputeManager/disputes/create.t.sol @@ -3,8 +3,8 @@ pragma solidity 0.8.26; import "forge-std/Test.sol"; -import { IDisputeManager } from "../../contracts/interfaces/IDisputeManager.sol"; -import { DisputeManagerTest } from "./DisputeManager.t.sol"; +import { IDisputeManager } from "../../../contracts/interfaces/IDisputeManager.sol"; +import { DisputeManagerTest } from "../DisputeManager.t.sol"; contract DisputeManagerCreateDisputeTest is DisputeManagerTest { diff --git a/packages/subgraph-service/test/disputes/draw.t.sol b/packages/subgraph-service/test/disputeManager/disputes/draw.t.sol similarity index 94% rename from packages/subgraph-service/test/disputes/draw.t.sol rename to packages/subgraph-service/test/disputeManager/disputes/draw.t.sol index e5ef827de..75dabaa2f 100644 --- a/packages/subgraph-service/test/disputes/draw.t.sol +++ b/packages/subgraph-service/test/disputeManager/disputes/draw.t.sol @@ -4,8 +4,8 @@ pragma solidity 0.8.26; import "forge-std/Test.sol"; import { PPMMath } from "@graphprotocol/horizon/contracts/libraries/PPMMath.sol"; -import { IDisputeManager } from "../../contracts/interfaces/IDisputeManager.sol"; -import { DisputeManagerTest } from "./DisputeManager.t.sol"; +import { IDisputeManager } from "../../../contracts/interfaces/IDisputeManager.sol"; +import { DisputeManagerTest } from "../DisputeManager.t.sol"; contract DisputeManagerDrawDisputeTest is DisputeManagerTest { diff --git a/packages/subgraph-service/test/disputeManager/governance/fishermanRewardCut.t.sol b/packages/subgraph-service/test/disputeManager/governance/fishermanRewardCut.t.sol new file mode 100644 index 000000000..0c821e2a2 --- /dev/null +++ b/packages/subgraph-service/test/disputeManager/governance/fishermanRewardCut.t.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import "forge-std/Test.sol"; + +import { IDisputeManager } from "../../../contracts/interfaces/IDisputeManager.sol"; +import { DisputeManagerTest } from "../DisputeManager.t.sol"; +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract DisputeManagerGovernanceFishermanRewardCutTest is DisputeManagerTest { + + /* + * TESTS + */ + + function test_Governance_SetFishermanRewardCut() public useGovernor { + uint32 fishermanRewardCut = 1000; + disputeManager.setFishermanRewardCut(fishermanRewardCut); + assertEq(disputeManager.fishermanRewardCut(), fishermanRewardCut, "Fisherman reward cut should be set."); + } + + function test_Governance_RevertWhen_OverMaximumValue(uint32 fishermanRewardCut) public useGovernor { + vm.assume(fishermanRewardCut > disputeManager.MAX_FISHERMAN_REWARD_CUT()); + vm.expectRevert(abi.encodeWithSelector(IDisputeManager.DisputeManagerInvalidFishermanReward.selector, fishermanRewardCut)); + disputeManager.setFishermanRewardCut(fishermanRewardCut); + } + + function test_Governance_RevertWhen_NotGovernor() public useFisherman { + uint32 fishermanRewardCut = 1000; + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, users.fisherman)); + disputeManager.setFishermanRewardCut(fishermanRewardCut); + } +} \ No newline at end of file