Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: opt in and out of strategy rewards #14

Merged
merged 7 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/BalanceForwarder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
pragma solidity ^0.8.0;

import {IBalanceForwarder} from "./interface/IBalanceForwarder.sol";
import {IBalanceTracker} from "./interface/IBalanceTracker.sol";
import {IBalanceTracker} from "reward-streams/interfaces/IBalanceTracker.sol";

/// @title BalanceForwarderModule
/// @title BalanceForwarder contract
/// @custom:security-contact [email protected]
/// @author Euler Labs (https://www.eulerlabs.com/)
/// @notice A generic contract to integrate with https://github.com/euler-xyz/reward-streams
Expand Down
46 changes: 39 additions & 7 deletions src/FourSixTwoSixAgg.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol";
import {ERC4626, IERC4626, Math} from "@openzeppelin/token/ERC20/extensions/ERC4626.sol";
import {AccessControlEnumerable} from "@openzeppelin/access/AccessControlEnumerable.sol";
import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol";
import {BalanceForwarder} from "./BalanceForwarder.sol";
import {BalanceForwarder, IBalanceForwarder} from "./BalanceForwarder.sol";
import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol";

/// @dev Do NOT use with fee on transfer tokens
/// @dev Do NOT use with rebasing tokens
Expand Down Expand Up @@ -45,9 +46,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn
bytes32 public constant STRATEGY_ADDER_ROLE_ADMIN_ROLE = keccak256("STRATEGY_ADDER_ROLE_ADMIN_ROLE");
bytes32 public constant STRATEGY_REMOVER_ROLE = keccak256("STRATEGY_REMOVER_ROLE");
bytes32 public constant STRATEGY_REMOVER_ROLE_ADMIN_ROLE = keccak256("STRATEGY_REMOVER_ROLE_ADMIN_ROLE");
bytes32 public constant PERFORMANCE_FEE_MANAGER_ROLE = keccak256("PERFORMANCE_FEE_MANAGER_ROLE");
bytes32 public constant PERFORMANCE_FEE_MANAGER_ROLE_ADMIN_ROLE =
keccak256("PERFORMANCE_FEE_MANAGER_ROLE_ADMIN_ROLE");
bytes32 public constant TREASURY_MANAGER_ROLE = keccak256("TREASURY_MANAGER_ROLE");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the role here, called it TREASURY to basically manage performance fee and interact with strategy underlying reward stream.

bytes32 public constant TREASURY_MANAGER_ROLE_ADMIN_ROLE = keccak256("TREASURY_MANAGER_ROLE_ADMIN_ROLE");

/// @dev The maximum performanceFee the vault can have is 50%
uint256 internal constant MAX_PERFORMANCE_FEE = 0.5e18;
Expand Down Expand Up @@ -150,27 +150,59 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn
_setRoleAdmin(WITHDRAW_QUEUE_REORDERER_ROLE, WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE);
_setRoleAdmin(STRATEGY_ADDER_ROLE, STRATEGY_ADDER_ROLE_ADMIN_ROLE);
_setRoleAdmin(STRATEGY_REMOVER_ROLE, STRATEGY_REMOVER_ROLE_ADMIN_ROLE);
_setRoleAdmin(PERFORMANCE_FEE_MANAGER_ROLE, PERFORMANCE_FEE_MANAGER_ROLE_ADMIN_ROLE);
_setRoleAdmin(TREASURY_MANAGER_ROLE, TREASURY_MANAGER_ROLE_ADMIN_ROLE);
}

/// @notice Set performance fee recipient address
/// @notice @param _newFeeRecipient Recipient address
function setFeeRecipient(address _newFeeRecipient) external onlyRole(PERFORMANCE_FEE_MANAGER_ROLE) {
function setFeeRecipient(address _newFeeRecipient) external onlyRole(TREASURY_MANAGER_ROLE) {
if (_newFeeRecipient == feeRecipient) revert FeeRecipientAlreadySet();

feeRecipient = _newFeeRecipient;
}

/// @notice Set performance fee (1e18 == 100%)
/// @notice @param _newFee Fee rate
function setPerformanceFee(uint256 _newFee) external onlyRole(PERFORMANCE_FEE_MANAGER_ROLE) {
function setPerformanceFee(uint256 _newFee) external onlyRole(TREASURY_MANAGER_ROLE) {
if (_newFee > MAX_PERFORMANCE_FEE) revert MaxPerformanceFeeExceeded();
if (feeRecipient == address(0)) revert FeeRecipientNotSet();
if (_newFee == performanceFee) revert PerformanceFeeAlreadySet();

performanceFee = _newFee;
}

/// @notice Opt in to strategy rewards
/// @param _strategy Strategy address
function optInStrategyRewards(address _strategy) external onlyRole(TREASURY_MANAGER_ROLE) {
if (!strategies[_strategy].active) revert InactiveStrategy();

IBalanceForwarder(_strategy).enableBalanceForwarder();
}

/// @notice Opt out of strategy rewards
/// @param _strategy Strategy address
function optOutStrategyRewards(address _strategy) external onlyRole(TREASURY_MANAGER_ROLE) {
IBalanceForwarder(_strategy).disableBalanceForwarder();
}

/// @notice Claim a specific strategy rewards
/// @param _strategy Strategy address.
/// @param _rewarded The address of the rewarded token.
/// @param _reward The address of the reward token.
/// @param _recipient The address to receive the claimed reward tokens.
/// @param _forfeitRecentReward Whether to forfeit the recent rewards and not update the accumulator.
function claimStrategyReward(
address _strategy,
address _rewarded,
address _reward,
address _recipient,
bool _forfeitRecentReward
) external onlyRole(TREASURY_MANAGER_ROLE) {
address rewardStreams = IBalanceForwarder(_strategy).balanceTrackerAddress();

IRewardStreams(rewardStreams).claimReward(_rewarded, _reward, _recipient, _forfeitRecentReward);
}

/// @notice Enables balance forwarding for sender
/// @dev Should call the IBalanceTracker hook with the current user's balance
function enableBalanceForwarder() external override nonReentrant {
Expand Down
19 changes: 0 additions & 19 deletions src/interface/IBalanceTracker.sol

This file was deleted.

12 changes: 6 additions & 6 deletions test/common/FourSixTwoSixAggBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ contract FourSixTwoSixAggBase is EVaultTestBase {
fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE(), deployer);
fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMIN_ROLE(), deployer);
fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMIN_ROLE(), deployer);
fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.PERFORMANCE_FEE_MANAGER_ROLE_ADMIN_ROLE(), deployer);
fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE_ADMIN_ROLE(), deployer);

// grant roles to manager
fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE(), manager);
fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE(), manager);
fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager);
fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager);
fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.PERFORMANCE_FEE_MANAGER_ROLE(), manager);
fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE(), manager);

vm.stopPrank();
}
Expand Down Expand Up @@ -74,21 +74,21 @@ contract FourSixTwoSixAggBase is EVaultTestBase {
fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMIN_ROLE()
);
assertEq(
fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.PERFORMANCE_FEE_MANAGER_ROLE()),
fourSixTwoSixAgg.PERFORMANCE_FEE_MANAGER_ROLE_ADMIN_ROLE()
fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE()),
fourSixTwoSixAgg.TREASURY_MANAGER_ROLE_ADMIN_ROLE()
);

assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE(), deployer));
assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE(), deployer));
assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMIN_ROLE(), deployer));
assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMIN_ROLE(), deployer));
assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.PERFORMANCE_FEE_MANAGER_ROLE_ADMIN_ROLE(), deployer));
assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE_ADMIN_ROLE(), deployer));

assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE(), manager));
assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE(), manager));
assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager));
assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager));
assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.PERFORMANCE_FEE_MANAGER_ROLE(), manager));
assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE(), manager));
}

function _addStrategy(address from, address strategy, uint256 allocationPoints) internal {
Expand Down
4 changes: 2 additions & 2 deletions test/e2e/BalanceForwarderE2ETest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ contract BalanceForwarderE2ETest is FourSixTwoSixAggBase {
fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE(), deployer);
fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMIN_ROLE(), deployer);
fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMIN_ROLE(), deployer);
fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.PERFORMANCE_FEE_MANAGER_ROLE_ADMIN_ROLE(), deployer);
fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE_ADMIN_ROLE(), deployer);

// grant roles to manager
fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE(), manager);
fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE(), manager);
fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager);
fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager);
fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.PERFORMANCE_FEE_MANAGER_ROLE(), manager);
fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE(), manager);
vm.stopPrank();

uint256 initialStrategyAllocationPoints = 500e18;
Expand Down
44 changes: 44 additions & 0 deletions test/e2e/StrategyRewardsE2ETest.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import {
FourSixTwoSixAggBase,
FourSixTwoSixAgg,
console2,
EVault,
IEVault,
IRMTestDefault,
TestERC20
} from "../common/FourSixTwoSixAggBase.t.sol";
import {TrackingRewardStreams} from "reward-streams/TrackingRewardStreams.sol";

contract StrategyRewardsE2ETest is FourSixTwoSixAggBase {
uint256 user1InitialBalance = 100000e18;

function setUp() public virtual override {
super.setUp();

uint256 initialStrategyAllocationPoints = 500e18;
_addStrategy(manager, address(eTST), initialStrategyAllocationPoints);

assetTST.mint(user1, user1InitialBalance);
}

function testOptInStrategyRewards() public {
vm.prank(manager);
fourSixTwoSixAgg.optInStrategyRewards(address(eTST));

assertTrue(eTST.balanceForwarderEnabled(address(fourSixTwoSixAgg)));
}

function testOptOutStrategyRewards() public {
vm.prank(manager);
fourSixTwoSixAgg.optInStrategyRewards(address(eTST));
assertTrue(eTST.balanceForwarderEnabled(address(fourSixTwoSixAgg)));

vm.prank(manager);
fourSixTwoSixAgg.optOutStrategyRewards(address(eTST));

assertFalse(eTST.balanceForwarderEnabled(address(fourSixTwoSixAgg)));
}
}
Loading