From 05bd032d3db6cf3e0c93223f5f79c84f4c5cec44 Mon Sep 17 00:00:00 2001 From: simplyoptimistic <111120814+simplyoptimistic@users.noreply.github.com> Date: Sat, 20 Jan 2024 20:58:25 +1100 Subject: [PATCH] feat: gauge notify admin (#127) --- contracts/gauge/CLGauge.sol | 7 +++-- contracts/gauge/CLGaugeFactory.sol | 11 +++++++ contracts/gauge/interfaces/ICLGauge.sol | 4 +++ .../gauge/interfaces/ICLGaugeFactory.sol | 9 ++++++ script/DeployCL.s.sol | 3 ++ script/constants/Optimism.json | 1 + test/BaseFixture.sol | 1 + .../fork/notifyRewardAmountWithoutClaim.t.sol | 4 +-- .../CLGauge/notifyRewardWithoutClaim.t.sol | 4 +-- .../concrete/CLGaugeFactory/createGauge.t.sol | 1 + .../CLGaugeFactory/setNotifyAdmin.t.sol | 29 +++++++++++++++++++ test/unit/concrete/script/DeployCL.t.sol | 3 ++ 12 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 test/unit/concrete/CLGaugeFactory/setNotifyAdmin.t.sol diff --git a/contracts/gauge/CLGauge.sol b/contracts/gauge/CLGauge.sol index 44c9880..48899c7 100644 --- a/contracts/gauge/CLGauge.sol +++ b/contracts/gauge/CLGauge.sol @@ -6,8 +6,8 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/ERC721Holder.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; import {ICLGauge} from "contracts/gauge/interfaces/ICLGauge.sol"; +import {ICLGaugeFactory} from "contracts/gauge/interfaces/ICLGaugeFactory.sol"; import {IVoter} from "contracts/core/interfaces/IVoter.sol"; -import {IVotingEscrow} from "contracts/core/interfaces/IVotingEscrow.sol"; import {ICLPool} from "contracts/core/interfaces/ICLPool.sol"; import {INonfungiblePositionManager} from "contracts/periphery/interfaces/INonfungiblePositionManager.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; @@ -29,6 +29,8 @@ contract CLGauge is ICLGauge, ERC721Holder, ReentrancyGuard { IVoter public override voter; /// @inheritdoc ICLGauge ICLPool public override pool; + /// @inheritdoc ICLGauge + ICLGaugeFactory public override gaugeFactory; /// @inheritdoc ICLGauge address public override feesVotingReward; @@ -78,6 +80,7 @@ contract CLGauge is ICLGauge, ERC721Holder, ReentrancyGuard { bool _isPool ) external override { require(address(pool) == address(0), "AI"); + gaugeFactory = ICLGaugeFactory(msg.sender); pool = ICLPool(_pool); feesVotingReward = _feesVotingReward; rewardToken = _rewardToken; @@ -335,7 +338,7 @@ contract CLGauge is ICLGauge, ERC721Holder, ReentrancyGuard { /// @inheritdoc ICLGauge function notifyRewardWithoutClaim(uint256 _amount) external override nonReentrant { address sender = msg.sender; - require(sender == IVotingEscrow(voter.ve()).team(), "NT"); + require(sender == gaugeFactory.notifyAdmin(), "NA"); require(_amount != 0, "ZR"); _notifyRewardAmount(sender, _amount); } diff --git a/contracts/gauge/CLGaugeFactory.sol b/contracts/gauge/CLGaugeFactory.sol index 78f8d3b..52f71ef 100644 --- a/contracts/gauge/CLGaugeFactory.sol +++ b/contracts/gauge/CLGaugeFactory.sol @@ -13,14 +13,25 @@ contract CLGaugeFactory is ICLGaugeFactory { address public immutable override implementation; /// @inheritdoc ICLGaugeFactory address public override nft; + /// @inheritdoc ICLGaugeFactory + address public override notifyAdmin; address private owner; constructor(address _voter, address _implementation) { voter = _voter; owner = msg.sender; + notifyAdmin = msg.sender; implementation = _implementation; } + /// @inheritdoc ICLGaugeFactory + function setNotifyAdmin(address _admin) external override { + require(notifyAdmin == msg.sender, "NA"); + require(_admin != address(0), "ZA"); + notifyAdmin = _admin; + emit SetNotifyAdmin(_admin); + } + /// @inheritdoc ICLGaugeFactory function setNonfungiblePositionManager(address _nft) external override { require(nft == address(0), "AI"); diff --git a/contracts/gauge/interfaces/ICLGauge.sol b/contracts/gauge/interfaces/ICLGauge.sol index 5d008ee..508d0b9 100644 --- a/contracts/gauge/interfaces/ICLGauge.sol +++ b/contracts/gauge/interfaces/ICLGauge.sol @@ -4,6 +4,7 @@ pragma solidity =0.7.6; import {INonfungiblePositionManager} from "contracts/periphery/interfaces/INonfungiblePositionManager.sol"; import {IVoter} from "contracts/core/interfaces/IVoter.sol"; import {ICLPool} from "contracts/core/interfaces/ICLPool.sol"; +import {ICLGaugeFactory} from "contracts/gauge/interfaces/ICLGaugeFactory.sol"; interface ICLGauge { event NotifyReward(address indexed from, uint256 amount); @@ -21,6 +22,9 @@ interface ICLGauge { /// @notice Address of the CL pool linked to the gauge function pool() external view returns (ICLPool); + /// @notice Address of the factory that created this gauge + function gaugeFactory() external view returns (ICLGaugeFactory); + /// @notice Address of the FeesVotingReward contract linked to the gauge function feesVotingReward() external view returns (address); diff --git a/contracts/gauge/interfaces/ICLGaugeFactory.sol b/contracts/gauge/interfaces/ICLGaugeFactory.sol index 7690179..0250b8a 100644 --- a/contracts/gauge/interfaces/ICLGaugeFactory.sol +++ b/contracts/gauge/interfaces/ICLGaugeFactory.sol @@ -2,6 +2,8 @@ pragma solidity =0.7.6; interface ICLGaugeFactory { + event SetNotifyAdmin(address indexed notifyAdmin); + /// @notice Address of the voter contract function voter() external view returns (address); @@ -11,11 +13,18 @@ interface ICLGaugeFactory { /// @notice Address of the NonfungiblePositionManager used to create nfts that gauges will accept function nft() external view returns (address); + /// @notice Administrator that can call `notifyRewardWithoutClaim` on gauges + function notifyAdmin() external view returns (address); + /// @notice Set Nonfungible Position Manager /// @dev Callable once only on initialize /// @param _nft The nonfungible position manager that will manage positions for this Factory function setNonfungiblePositionManager(address _nft) external; + /// @notice Set notifyAdmin value on gauge factory + /// @param _admin New administrator that will be able to call `notifyRewardWithoutClaim` on gauges. + function setNotifyAdmin(address _admin) external; + /// @notice Called by the voter contract via factory.createPool /// @param _forwarder The address of the forwarder contract /// @param _pool The address of the pool diff --git a/script/DeployCL.s.sol b/script/DeployCL.s.sol index 4f9cdfb..74422e6 100644 --- a/script/DeployCL.s.sol +++ b/script/DeployCL.s.sol @@ -31,6 +31,7 @@ contract DeployCL is Script { address public factoryRegistry; address public poolFactoryOwner; address public feeManager; + address public notifyAdmin; address public factoryV2; // deployed contracts @@ -57,6 +58,7 @@ contract DeployCL is Script { factoryRegistry = abi.decode(vm.parseJson(jsonConstants, ".FactoryRegistry"), (address)); poolFactoryOwner = abi.decode(vm.parseJson(jsonConstants, ".poolFactoryOwner"), (address)); feeManager = abi.decode(vm.parseJson(jsonConstants, ".feeManager"), (address)); + notifyAdmin = abi.decode(vm.parseJson(jsonConstants, ".notifyAdmin"), (address)); factoryV2 = abi.decode(vm.parseJson(jsonConstants, ".factoryV2"), (address)); require(address(voter) != address(0)); // sanity check for constants file fillled out correctly @@ -87,6 +89,7 @@ contract DeployCL is Script { // set nft manager in the factories gaugeFactory.setNonfungiblePositionManager(address(nft)); + gaugeFactory.setNotifyAdmin(notifyAdmin); poolFactory.setNonfungiblePositionManager(address(nft)); // deploy fee modules diff --git a/script/constants/Optimism.json b/script/constants/Optimism.json index 28a7768..0c0ae69 100644 --- a/script/constants/Optimism.json +++ b/script/constants/Optimism.json @@ -1,6 +1,7 @@ { "poolFactoryOwner": "0x0000000000000000000000000000000000000001", "feeManager": "0x0000000000000000000000000000000000000001", + "notifyAdmin": "0x0000000000000000000000000000000000000001", "FactoryRegistry": "0x062C88B4ba954955746eDA6f475C26eeaC04614B", "Voter": "0x8b50264507f84Edc75087aeAf63a016E89b7a441", "WETH": "0x4200000000000000000000000000000000000006", diff --git a/test/BaseFixture.sol b/test/BaseFixture.sol index a843b53..ef2c286 100644 --- a/test/BaseFixture.sol +++ b/test/BaseFixture.sol @@ -100,6 +100,7 @@ abstract contract BaseFixture is Test, Constants, Events, PoolUtils { // set nftmanager in the factories gaugeFactory.setNonfungiblePositionManager(address(nft)); + gaugeFactory.setNotifyAdmin(users.owner); poolFactory.setNonfungiblePositionManager(address(nft)); vm.stopPrank(); diff --git a/test/fork/notifyRewardAmountWithoutClaim.t.sol b/test/fork/notifyRewardAmountWithoutClaim.t.sol index d367269..4468165 100644 --- a/test/fork/notifyRewardAmountWithoutClaim.t.sol +++ b/test/fork/notifyRewardAmountWithoutClaim.t.sol @@ -59,8 +59,8 @@ contract NotifyRewardAmountWithoutClaimForkTest is BaseForkFixture { skipToNextEpoch(0); - vm.startPrank(escrow.team()); - deal(address(rewardToken), escrow.team(), 604_800); + vm.startPrank(users.owner); + deal(address(rewardToken), users.owner, 604_800); rewardToken.approve(address(gauge), 604_800); gauge.notifyRewardWithoutClaim(604_800); // requires minimum value of 604800 diff --git a/test/unit/concrete/CLGauge/notifyRewardWithoutClaim.t.sol b/test/unit/concrete/CLGauge/notifyRewardWithoutClaim.t.sol index 746b355..295ba59 100644 --- a/test/unit/concrete/CLGauge/notifyRewardWithoutClaim.t.sol +++ b/test/unit/concrete/CLGauge/notifyRewardWithoutClaim.t.sol @@ -27,9 +27,9 @@ contract NotifyRewardWithoutClaimTest is CLGaugeTest { skipToNextEpoch(0); } - function test_RevertIf_NotTeam() public { + function test_RevertIf_NotNotifyAdmin() public { vm.startPrank(users.charlie); - vm.expectRevert(abi.encodePacked("NT")); + vm.expectRevert(abi.encodePacked("NA")); gauge.notifyRewardWithoutClaim(TOKEN_1); } diff --git a/test/unit/concrete/CLGaugeFactory/createGauge.t.sol b/test/unit/concrete/CLGaugeFactory/createGauge.t.sol index 85ee73b..8c4a825 100644 --- a/test/unit/concrete/CLGaugeFactory/createGauge.t.sol +++ b/test/unit/concrete/CLGaugeFactory/createGauge.t.sol @@ -51,6 +51,7 @@ contract CreateGaugeTest is CLGaugeFactoryTest { assertEq(address(gauge.pool()), pool); assertEq(gauge.feesVotingReward(), address(feesVotingReward)); assertEq(gauge.rewardToken(), address(rewardToken)); + assertEq(address(gauge.gaugeFactory()), address(gaugeFactory)); assertEq(gauge.isPool(), true); } } diff --git a/test/unit/concrete/CLGaugeFactory/setNotifyAdmin.t.sol b/test/unit/concrete/CLGaugeFactory/setNotifyAdmin.t.sol new file mode 100644 index 0000000..19d68f3 --- /dev/null +++ b/test/unit/concrete/CLGaugeFactory/setNotifyAdmin.t.sol @@ -0,0 +1,29 @@ +pragma solidity ^0.7.6; +pragma abicoder v2; + +import "./CLGaugeFactory.t.sol"; + +contract SetNotifyAdminTest is CLGaugeFactoryTest { + event SetNotifyAdmin(address indexed notifyAdmin); + + function test_RevertIf_NotNotifyAdmin() public { + vm.startPrank({msgSender: users.alice}); + vm.expectRevert(abi.encodePacked("NA")); + gaugeFactory.setNotifyAdmin({_admin: users.alice}); + } + + function test_RevertIf_ZeroAddress() public { + vm.startPrank({msgSender: users.owner}); + vm.expectRevert(abi.encodePacked("ZA")); + gaugeFactory.setNotifyAdmin({_admin: address(0)}); + } + + function test_SetNotifyAdmin() public { + vm.prank({msgSender: users.owner}); + vm.expectEmit(true, false, false, false, address(gaugeFactory)); + emit SetNotifyAdmin(users.alice); + gaugeFactory.setNotifyAdmin({_admin: users.alice}); + + assertEq(gaugeFactory.notifyAdmin(), address(users.alice)); + } +} diff --git a/test/unit/concrete/script/DeployCL.t.sol b/test/unit/concrete/script/DeployCL.t.sol index 717033a..4943259 100644 --- a/test/unit/concrete/script/DeployCL.t.sol +++ b/test/unit/concrete/script/DeployCL.t.sol @@ -31,6 +31,7 @@ contract DeployCLTest is Test { address public factoryRegistry; address public poolFactoryOwner; address public feeManager; + address public notifyAdmin; // deployed contracts CLPool public poolImplementation; @@ -56,6 +57,7 @@ contract DeployCLTest is Test { factoryRegistry = abi.decode(vm.parseJson(jsonConstants, ".FactoryRegistry"), (address)); poolFactoryOwner = abi.decode(vm.parseJson(jsonConstants, ".poolFactoryOwner"), (address)); feeManager = abi.decode(vm.parseJson(jsonConstants, ".feeManager"), (address)); + notifyAdmin = abi.decode(vm.parseJson(jsonConstants, ".notifyAdmin"), (address)); deal(address(deployerAddress), 10 ether); } @@ -105,6 +107,7 @@ contract DeployCLTest is Test { assertEq(gaugeFactory.voter(), voter); assertEq(gaugeFactory.implementation(), address(gaugeImplementation)); assertEq(gaugeFactory.nft(), address(nft)); + assertEq(gaugeFactory.notifyAdmin(), notifyAdmin); assertTrue(address(swapFeeModule) != address(0)); assertEq(swapFeeModule.MAX_FEE(), 30_000); // 3%, using pip denomination