diff --git a/packages/evm/contracts/incentives/APointsIncentive.sol b/packages/evm/contracts/incentives/APointsIncentive.sol index 366bc1a8..a5d84130 100644 --- a/packages/evm/contracts/incentives/APointsIncentive.sol +++ b/packages/evm/contracts/incentives/APointsIncentive.sol @@ -6,6 +6,7 @@ import {Cloneable} from "contracts/shared/Cloneable.sol"; import {Budget} from "contracts/budgets/Budget.sol"; import {Incentive} from "./Incentive.sol"; +import {OwnableRoles} from "@solady/auth/OwnableRoles.sol"; /// @title Points Incentive /// @notice A simple on-chain points incentive implementation that allows claiming of soulbound tokens @@ -32,7 +33,13 @@ abstract contract APointsIncentive is Incentive { /// @notice Claim the incentive /// @param data_ The data payload for the incentive claim `(address recipient, bytes data)` /// @return True if the incentive was successfully claimed - function claim(bytes calldata data_) external override onlyOwner returns (bool) { + function claim(bytes calldata data_) external override returns (bool) { + // check ownership + OwnableRoles points = OwnableRoles(venue); + if (points.owner() != msg.sender && points.hasAnyRole(msg.sender, 1 << 1) != true) { + revert BoostError.Unauthorized(); + } + ClaimPayload memory claim_ = abi.decode(data_, (ClaimPayload)); if (!_isClaimable(claim_.target)) revert NotClaimable(); diff --git a/packages/evm/test/incentives/PointsIncentive.t.sol b/packages/evm/test/incentives/PointsIncentive.t.sol index 675de783..81865577 100644 --- a/packages/evm/test/incentives/PointsIncentive.t.sol +++ b/packages/evm/test/incentives/PointsIncentive.t.sol @@ -79,6 +79,15 @@ contract PointsIncentiveTest is Test { incentive.claim(abi.encode(Incentive.ClaimPayload({target: address(1), data: new bytes(0)}))); } + function test_claimAuthorized() public { + vm.expectCall(address(points), abi.encodeCall(points.issue, (address(1), 100)), 1); + points.grantRoles(address(0xdeadbeef), points.ISSUER_ROLE()); + + vm.prank(address(0xdeadbeef)); + incentive.claim(abi.encode(Incentive.ClaimPayload({target: address(1), data: new bytes(0)}))); + assertEq(points.balanceOf(address(1)), 100); + } + //////////////////////////////////// // PointsIncentive.getComponentInterface // ////////////////////////////////////