diff --git a/src/notifiers/TransferRewardNotifier.sol b/src/notifiers/TransferRewardNotifier.sol index b4c32d8..0e1f612 100644 --- a/src/notifiers/TransferRewardNotifier.sol +++ b/src/notifiers/TransferRewardNotifier.sol @@ -17,6 +17,11 @@ import {SafeERC20} from "openzeppelin/token/ERC20/utils/SafeERC20.sol"; contract TransferRewardNotifier is RewardTokenNotifierBase { using SafeERC20 for IERC20; + /// @notice Emitted when the owner approves an address to spend tokens. + /// @param spender The address being granted approval. + /// @param amount The amount of tokens approved for spending. + event Approved(address indexed spender, uint256 amount); + /// @param _receiver The contract that will receive reward notifications, typically an instance /// of Staker. /// @param _initialRewardAmount The initial amount of reward tokens to be distributed per @@ -34,6 +39,17 @@ contract TransferRewardNotifier is RewardTokenNotifierBase { Ownable(_initialOwner) {} + /// @notice Approves an address to transferFrom tokens held by this contract. + /// @param _spender The address to approve for token spending. + /// @param _amount The amount of tokens to approve. + /// @dev Caller must be the contract owner. This enables tokens to be clawed back to end rewards + /// as desired. + function approve(address _spender, uint256 _amount) external { + _checkOwner(); + TOKEN.approve(_spender, _amount); + emit Approved(_spender, _amount); + } + /// @inheritdoc RewardTokenNotifierBase /// @dev Transfers exactly rewardAmount tokens from this contract's balance to the receiver /// using transfer. This contract must have a sufficient balance of reward tokens for the diff --git a/test/TransferRewardNotifier.t.sol b/test/TransferRewardNotifier.t.sol index 466666a..97b84a1 100644 --- a/test/TransferRewardNotifier.t.sol +++ b/test/TransferRewardNotifier.t.sol @@ -230,3 +230,33 @@ contract Notify is TransferRewardNotifierTest { } } } + +contract Approve is TransferRewardNotifierTest { + function testFuzz_ApprovesTheSpenderForTheAmount(address _spender, uint256 _amount) public { + vm.assume(_spender != address(0)); + + vm.prank(owner); + notifier.approve(_spender, _amount); + + assertEq(token.allowance(address(notifier), _spender), _amount); + } + + function testFuzz_EmitsAnApprovedEvent(address _spender, uint256 _amount) public { + vm.assume(_spender != address(0)); + + vm.expectEmit(); + emit TransferRewardNotifier.Approved(_spender, _amount); + vm.prank(owner); + notifier.approve(_spender, _amount); + } + + function testFuzz_RevertIf_CallerIsNotOwner(address _spender, uint256 _amount, address _notOwner) + public + { + vm.assume(_spender != address(0) && _notOwner != owner); + + vm.prank(_notOwner); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, _notOwner)); + notifier.approve(_spender, _amount); + } +}