From c8ab40bf1c6845dc84e0579046bb30a3017c91f7 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 11 Jun 2024 15:35:28 +0300 Subject: [PATCH 1/8] add Rebalancer.sol --- src/FourSixTwoSixAgg.sol | 133 ++++-------------- src/Rebalancer.sol | 90 ++++++++++++ src/interface/IFourSixTwoSixAgg.sol | 23 +++ test/common/FourSixTwoSixAggBase.t.sol | 7 + ...positRebalanceHarvestWithdrawE2ETest.t.sol | 12 +- test/e2e/PerformanceFeeE2ETest.t.sol | 2 +- test/e2e/StrategyCapE2ETest.t.sol | 6 +- test/unit/GulpTest.t.sol | 2 +- test/unit/HarvestTest.t.sol | 2 +- test/unit/RebalanceTest.t.sol | 18 +-- 10 files changed, 169 insertions(+), 126 deletions(-) create mode 100644 src/Rebalancer.sol create mode 100644 src/interface/IFourSixTwoSixAgg.sol diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 000b4307..f48ae71c 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; +// external dep +// internal dep +import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; import {Context} from "@openzeppelin/utils/Context.sol"; import {ERC20, IERC20} from "@openzeppelin/token/ERC20/ERC20.sol"; import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol"; @@ -16,7 +19,7 @@ import {Hooks} from "./Hooks.sol"; /// @dev Do NOT use with rebasing tokens /// @dev Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol /// @dev inspired by Yearn v3 ❤️ -contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEnumerable, Hooks { +contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC4626, AccessControlEnumerable, Hooks { using SafeERC20 for IERC20; using SafeCast for uint256; @@ -51,6 +54,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn bytes32 public constant STRATEGY_REMOVER_ROLE_ADMINROLE = keccak256("STRATEGY_REMOVER_ROLE_ADMINROLE"); bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); bytes32 public constant MANAGER_ROLE_ADMINROLE = keccak256("MANAGER_ROLE_ADMINROLE"); + bytes32 public constant REBALANCER_ROLE = keccak256("REBALANCER_ROLE"); + bytes32 public constant REBALANCER_ROLE_ADMINROLE = keccak256("REBALANCER_ROLE_ADMINROLE"); /// @dev The maximum performanceFee the vault can have is 50% uint256 internal constant MAX_PERFORMANCE_FEE = 0.5e18; @@ -82,18 +87,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn uint8 locked; } - /// @dev A struct that hold a strategy allocation's config - /// allocated: amount of asset deposited into strategy - /// allocationPoints: number of points allocated to this strategy - /// active: a boolean to indice if this strategy is active or not - /// cap: an optional cap in terms of deposited underlying asset. By default, it is set to 0(not activated) - struct Strategy { - uint120 allocated; - uint120 allocationPoints; - bool active; - uint120 cap; - } - event SetFeeRecipient(address indexed oldRecipient, address indexed newRecipient); event SetPerformanceFee(uint256 oldFee, uint256 newFee); event OptInStrategyRewards(address indexed strategy); @@ -256,24 +249,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn _disableBalanceForwarder(_msgSender()); } - /// @notice Rebalance strategy's allocation. - /// @param _strategy Address of strategy to rebalance. - function rebalance(address _strategy) external nonReentrant { - _rebalance(_strategy); - - _gulp(); - } - - /// @notice Rebalance multiple strategies. - /// @param _strategies An array of strategy addresses to rebalance. - function rebalanceMultipleStrategies(address[] calldata _strategies) external nonReentrant { - for (uint256 i; i < _strategies.length; ++i) { - _rebalance(_strategies[i]); - } - - _gulp(); - } - /// @notice Harvest strategy. /// @param strategy address of strategy function harvest(address strategy) external nonReentrant { @@ -291,6 +266,28 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn _gulp(); } + function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) + external + nonReentrant + onlyRole(REBALANCER_ROLE) + { + _harvest(_strategy); + + Strategy memory strategyData = strategies[_strategy]; + + if (_isDeposit) { + // Do required approval (safely) and deposit + IERC20(asset()).safeApprove(_strategy, _amountToRebalance); + IERC4626(_strategy).deposit(_amountToRebalance, address(this)); + strategies[_strategy].allocated = uint120(strategyData.allocated + _amountToRebalance); + totalAllocated += _amountToRebalance; + } else { + IERC4626(_strategy).withdraw(_amountToRebalance, address(this), address(this)); + strategies[_strategy].allocated = (strategyData.allocated - _amountToRebalance).toUint120(); + totalAllocated -= _amountToRebalance; + } + } + /// @notice Adjust a certain strategy's allocation points. /// @dev Can only be called by an address that have the STRATEGY_MANAGER_ROLE /// @param _strategy address of strategy @@ -645,80 +642,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn emit Gulp(esrSlotCache.interestLeft, esrSlotCache.interestSmearEnd); } - /// @notice Rebalance strategy allocation. - /// @dev This function will first harvest yield, gulps and update interest. - /// @dev If current allocation is greater than target allocation, the aggregator will withdraw the excess assets. - /// If current allocation is less than target allocation, the aggregator will: - /// - Try to deposit the delta, if the cash is not sufficient, deposit all the available cash - /// - If all the available cash is greater than the max deposit, deposit the max deposit - /// @param _strategy Address of strategy to rebalance. - function _rebalance(address _strategy) internal { - if (_strategy == address(0)) { - return; //nothing to rebalance as this is the cash reserve - } - - _callHookTarget(REBALANCE, _msgSender()); - - _harvest(_strategy); - - Strategy memory strategyData = strategies[_strategy]; - - // no rebalance if strategy have an allocated amount greater than cap - if ((strategyData.cap > 0) && (strategyData.allocated >= strategyData.cap)) return; - - uint256 totalAllocationPointsCache = totalAllocationPoints; - uint256 totalAssetsAllocatableCache = totalAssetsAllocatable(); - uint256 targetAllocation = - totalAssetsAllocatableCache * strategyData.allocationPoints / totalAllocationPointsCache; - - if ((strategyData.cap > 0) && (targetAllocation > strategyData.cap)) targetAllocation = strategyData.cap; - - uint256 amountToRebalance; - if (strategyData.allocated > targetAllocation) { - // Withdraw - amountToRebalance = strategyData.allocated - targetAllocation; - - uint256 maxWithdraw = IERC4626(_strategy).maxWithdraw(address(this)); - if (amountToRebalance > maxWithdraw) { - amountToRebalance = maxWithdraw; - } - - IERC4626(_strategy).withdraw(amountToRebalance, address(this), address(this)); - strategies[_strategy].allocated = (strategyData.allocated - amountToRebalance).toUint120(); - totalAllocated -= amountToRebalance; - } else if (strategyData.allocated < targetAllocation) { - // Deposit - uint256 targetCash = - totalAssetsAllocatableCache * strategies[address(0)].allocationPoints / totalAllocationPointsCache; - uint256 currentCash = totalAssetsAllocatableCache - totalAllocated; - - // Calculate available cash to put in strategies - uint256 cashAvailable = (currentCash > targetCash) ? currentCash - targetCash : 0; - - amountToRebalance = targetAllocation - strategyData.allocated; - if (amountToRebalance > cashAvailable) { - amountToRebalance = cashAvailable; - } - - uint256 maxDeposit = IERC4626(_strategy).maxDeposit(address(this)); - if (amountToRebalance > maxDeposit) { - amountToRebalance = maxDeposit; - } - - if (amountToRebalance == 0) { - return; // No cash to deposit - } - - // Do required approval (safely) and deposit - IERC20(asset()).safeApprove(_strategy, amountToRebalance); - IERC4626(_strategy).deposit(amountToRebalance, address(this)); - strategies[_strategy].allocated = uint120(strategyData.allocated + amountToRebalance); - totalAllocated += amountToRebalance; - } - - emit Rebalance(_strategy, strategyData.allocated, targetAllocation, amountToRebalance); - } - function _harvest(address _strategy) internal { Strategy memory strategyData = strategies[_strategy]; diff --git a/src/Rebalancer.sol b/src/Rebalancer.sol new file mode 100644 index 00000000..479892ef --- /dev/null +++ b/src/Rebalancer.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; +import {IERC4626} from "@openzeppelin/token/ERC20/extensions/ERC4626.sol"; + +contract Rebalancer { + /// @notice Rebalance strategy allocation. + /// @dev This function will first harvest yield, gulps and update interest. + /// @dev If current allocation is greater than target allocation, the aggregator will withdraw the excess assets. + /// If current allocation is less than target allocation, the aggregator will: + /// - Try to deposit the delta, if the cash is not sufficient, deposit all the available cash + /// - If all the available cash is greater than the max deposit, deposit the max deposit + function rebalance(address _curatedVault, address _strategy) external { + _rebalance(_curatedVault, _strategy); + } + + function rebalanceMultipleStrategies(address _curatedVault, address[] calldata strategies) external { + for (uint256 i; i < strategies.length; ++i) { + _rebalance(_curatedVault, strategies[i]); + } + } + + function _rebalance(address _curatedVault, address _strategy) private { + if (_strategy == address(0)) { + return; //nothing to rebalance as that's the cash reserve + } + + // _callHookTarget(REBALANCE, _msgSender()); + + IFourSixTwoSixAgg.Strategy memory strategyData = IFourSixTwoSixAgg(_curatedVault).getStrategy(_strategy); + + // no rebalance if strategy have an allocated amount greater than cap + if ((strategyData.cap > 0) && (strategyData.allocated >= strategyData.cap)) return; + + uint256 totalAllocationPointsCache = IFourSixTwoSixAgg(_curatedVault).totalAllocationPoints(); + uint256 totalAssetsAllocatableCache = IFourSixTwoSixAgg(_curatedVault).totalAssetsAllocatable(); + uint256 targetAllocation = + totalAssetsAllocatableCache * strategyData.allocationPoints / totalAllocationPointsCache; + + if ((strategyData.cap > 0) && (targetAllocation > strategyData.cap)) targetAllocation = strategyData.cap; + + uint256 amountToRebalance; + if (strategyData.allocated > targetAllocation) { + // Withdraw + amountToRebalance = strategyData.allocated - targetAllocation; + + uint256 maxWithdraw = IERC4626(_strategy).maxWithdraw(address(this)); + if (amountToRebalance > maxWithdraw) { + amountToRebalance = maxWithdraw; + } + + IFourSixTwoSixAgg(_curatedVault).rebalance(_strategy, amountToRebalance, false); + } else if (strategyData.allocated < targetAllocation) { + // Deposit + uint256 targetCash = totalAssetsAllocatableCache + * IFourSixTwoSixAgg(_curatedVault).getStrategy(address(0)).allocationPoints / totalAllocationPointsCache; + uint256 currentCash = totalAssetsAllocatableCache - IFourSixTwoSixAgg(_curatedVault).totalAllocated(); + + // Calculate available cash to put in strategies + uint256 cashAvailable = (currentCash > targetCash) ? currentCash - targetCash : 0; + + amountToRebalance = targetAllocation - strategyData.allocated; + if (amountToRebalance > cashAvailable) { + amountToRebalance = cashAvailable; + } + + uint256 maxDeposit = IERC4626(_strategy).maxDeposit(address(this)); + if (amountToRebalance > maxDeposit) { + amountToRebalance = maxDeposit; + } + + if (amountToRebalance == 0) { + return; // No cash to deposit + } + + IFourSixTwoSixAgg(_curatedVault).rebalance(_strategy, amountToRebalance, true); + } + + // emit Rebalance(_strategy, strategyData.allocated, targetAllocation, amountToRebalance) + } + + // /// @notice Rebalance strategy's allocation. + // /// @param _strategy Address of strategy to rebalance. + // function rebalance(address _strategy) external { + // _rebalance(_strategy); + + // _gulp(); + // } +} diff --git a/src/interface/IFourSixTwoSixAgg.sol b/src/interface/IFourSixTwoSixAgg.sol new file mode 100644 index 00000000..68dcc390 --- /dev/null +++ b/src/interface/IFourSixTwoSixAgg.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +interface IFourSixTwoSixAgg { + /// @dev A struct that hold a strategy allocation's config + /// allocated: amount of asset deposited into strategy + /// allocationPoints: number of points allocated to this strategy + /// active: a boolean to indice if this strategy is active or not + /// cap: an optional cap in terms of deposited underlying asset. By default, it is set to 0(not activated) + struct Strategy { + uint120 allocated; + uint120 allocationPoints; + bool active; + uint120 cap; + } + + function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) external; + + function getStrategy(address _strategy) external view returns (Strategy memory); + function totalAllocationPoints() external view returns (uint256); + function totalAllocated() external view returns (uint256); + function totalAssetsAllocatable() external view returns (uint256); +} diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol index b605561c..223f7ff9 100644 --- a/test/common/FourSixTwoSixAggBase.t.sol +++ b/test/common/FourSixTwoSixAggBase.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import "evk/test/unit/evault/EVaultTestBase.t.sol"; import {FourSixTwoSixAgg} from "../../src/FourSixTwoSixAgg.sol"; +import {Rebalancer} from "../../src/Rebalancer.sol"; import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; import {Hooks} from "../../src/Hooks.sol"; @@ -15,6 +16,7 @@ contract FourSixTwoSixAggBase is EVaultTestBase { address manager; FourSixTwoSixAgg fourSixTwoSixAgg; + Rebalancer rebalancer; function setUp() public virtual override { super.setUp(); @@ -24,6 +26,7 @@ contract FourSixTwoSixAggBase is EVaultTestBase { user2 = makeAddr("User_2"); vm.startPrank(deployer); + rebalancer = new Rebalancer(); fourSixTwoSixAgg = new FourSixTwoSixAgg( evc, address(0), @@ -41,6 +44,7 @@ contract FourSixTwoSixAggBase is EVaultTestBase { fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMINROLE(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMINROLE(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER_ROLE_ADMINROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.REBALANCER_ROLE_ADMINROLE(), deployer); // grant roles to manager fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE(), manager); @@ -49,6 +53,9 @@ contract FourSixTwoSixAggBase is EVaultTestBase { fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER_ROLE(), manager); + // grant rebalancing role + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.REBALANCER_ROLE(), address(rebalancer)); + vm.stopPrank(); } diff --git a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol index 2da74da6..3bd66ae9 100644 --- a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol @@ -55,7 +55,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); @@ -134,7 +134,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); @@ -233,7 +233,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - fourSixTwoSixAgg.rebalanceMultipleStrategies(strategiesToRebalance); + rebalancer.rebalanceMultipleStrategies(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedeTSTStrategyCash); @@ -319,7 +319,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); @@ -421,7 +421,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - fourSixTwoSixAgg.rebalanceMultipleStrategies(strategiesToRebalance); + rebalancer.rebalanceMultipleStrategies(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedeTSTStrategyCash); @@ -552,7 +552,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - fourSixTwoSixAgg.rebalanceMultipleStrategies(strategiesToRebalance); + rebalancer.rebalanceMultipleStrategies(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedeTSTStrategyCash); diff --git a/test/e2e/PerformanceFeeE2ETest.t.sol b/test/e2e/PerformanceFeeE2ETest.t.sol index 238dbb93..ece89ae4 100644 --- a/test/e2e/PerformanceFeeE2ETest.t.sol +++ b/test/e2e/PerformanceFeeE2ETest.t.sol @@ -80,7 +80,7 @@ contract PerformanceFeeE2ETest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); diff --git a/test/e2e/StrategyCapE2ETest.t.sol b/test/e2e/StrategyCapE2ETest.t.sol index a364f8d0..1a9172b6 100644 --- a/test/e2e/StrategyCapE2ETest.t.sol +++ b/test/e2e/StrategyCapE2ETest.t.sol @@ -80,7 +80,7 @@ contract StrategyCapE2ETest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); @@ -98,7 +98,7 @@ contract StrategyCapE2ETest is FourSixTwoSixAggBase { uint256 strategyAllocatedBefore = (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated; - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); vm.stopPrank(); assertEq(strategyAllocatedBefore, (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated); @@ -141,7 +141,7 @@ contract StrategyCapE2ETest is FourSixTwoSixAggBase { fourSixTwoSixAgg.setStrategyCap(address(eTST), cap); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); assertEq(fourSixTwoSixAgg.totalAllocated(), cap); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), cap); diff --git a/test/unit/GulpTest.t.sol b/test/unit/GulpTest.t.sol index 3f6da94d..2f826be4 100644 --- a/test/unit/GulpTest.t.sol +++ b/test/unit/GulpTest.t.sol @@ -44,7 +44,7 @@ contract GulpTest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); diff --git a/test/unit/HarvestTest.t.sol b/test/unit/HarvestTest.t.sol index c660dac4..52c6128e 100644 --- a/test/unit/HarvestTest.t.sol +++ b/test/unit/HarvestTest.t.sol @@ -44,7 +44,7 @@ contract HarvestTest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); diff --git a/test/unit/RebalanceTest.t.sol b/test/unit/RebalanceTest.t.sol index cbac406e..a5fd0d93 100644 --- a/test/unit/RebalanceTest.t.sol +++ b/test/unit/RebalanceTest.t.sol @@ -54,7 +54,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); @@ -100,7 +100,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { ); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); assertEq(fourSixTwoSixAgg.totalAllocated(), eTSTMaxDeposit); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), eTSTMaxDeposit); @@ -131,7 +131,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { // rebalance into first strategy vm.warp(block.timestamp + 86400); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); // create new strategy & add it IEVault eTSTsecondary; @@ -161,7 +161,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { uint256 expectedStrategyCash = currentCash - targetCash; vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTSTsecondary)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTSTsecondary)); // assertEq(fourSixTwoSixAgg.totalAllocated(), eTSTsecondaryMaxDeposit); assertEq( @@ -208,7 +208,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { ); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); assertEq(fourSixTwoSixAgg.totalAllocated(), strategyBefore.allocated); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); @@ -239,7 +239,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { // rebalance into strategy vm.warp(block.timestamp + 86400); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); // decrease allocation points uint256 newAllocationPoints = 300e18; @@ -256,7 +256,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); @@ -291,7 +291,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { // rebalance into strategy vm.warp(block.timestamp + 86400); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); // decrease allocation points uint256 newAllocationPoints = 300e18; @@ -314,7 +314,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { ); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); // assertEq(fourSixTwoSixAgg.totalAllocated(), strategyBefore.allocated - eTSTMaxWithdraw); // assertEq( From 707ed8fc1f6e1b07d936fdc38018032d27b72211 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 11 Jun 2024 15:41:53 +0300 Subject: [PATCH 2/8] remove modifier --- src/FourSixTwoSixAgg.sol | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index f48ae71c..a1b0c837 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -103,14 +103,6 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 event AccruePerformanceFee(address indexed feeRecipient, uint256 performanceFee, uint256 yield, uint256 feeShares); event SetStrategyCap(address indexed strategy, uint256 cap); - /// @notice Modifier to require an account status check on the EVC. - /// @dev Calls `requireAccountStatusCheck` function from EVC for the specified account after the function body. - /// @param account The address of the account to check. - modifier requireAccountStatusCheck(address account) { - _; - evc.requireAccountStatusCheck(account); - } - /// @dev Non reentrancy modifier for interest rate updates modifier nonReentrant() { if (esrSlot.locked == REENTRANCYLOCK__LOCKED) revert Reentrancy(); @@ -456,10 +448,13 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 public override (ERC20, IERC20) nonReentrant - requireAccountStatusCheck(_msgSender()) returns (bool) { - return super.transfer(to, amount); + super.transfer(to, amount); + + _requireAccountStatusCheck(_msgSender()); + + return true; } /// @notice Transfers a certain amount of tokens from a sender to a recipient. @@ -471,10 +466,13 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 public override (ERC20, IERC20) nonReentrant - requireAccountStatusCheck(from) returns (bool) { - return super.transferFrom(from, to, amount); + super.transferFrom(from, to, amount); + + _requireAccountStatusCheck(from); + + return true; } /// @dev See {IERC4626-deposit}. @@ -493,12 +491,13 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 public override nonReentrant - requireAccountStatusCheck(owner) returns (uint256 shares) { // Move interest to totalAssetsDeposited _updateInterestAccrued(); - return super.withdraw(assets, receiver, owner); + shares = super.withdraw(assets, receiver, owner); + + _requireAccountStatusCheck(owner); } /// @dev See {IERC4626-redeem}. @@ -507,12 +506,13 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 public override nonReentrant - requireAccountStatusCheck(owner) returns (uint256 assets) { // Move interest to totalAssetsDeposited _updateInterestAccrued(); - return super.redeem(shares, receiver, owner); + assets = super.redeem(shares, receiver, owner); + + _requireAccountStatusCheck(owner); } /// @notice Set hooks contract and hooked functions. @@ -736,4 +736,11 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 function _msgSender() internal view override (Context, EVCUtil) returns (address) { return EVCUtil._msgSender(); } + + /// @notice Function to require an account status check on the EVC. + /// @dev Calls `requireAccountStatusCheck` function from EVC for the specified account after the function body. + /// @param _account The address of the account to check. + function _requireAccountStatusCheck(address _account) private { + evc.requireAccountStatusCheck(_account); + } } From 6791fad20ae5d1dde8b8c1ccb132a6f323c3b55f Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 11 Jun 2024 16:21:07 +0300 Subject: [PATCH 3/8] more refactoring --- foundry.toml | 2 +- src/FourSixTwoSixAgg.sol | 81 ++++++++++++-------------- src/Rebalancer.sol | 13 +++-- src/interface/IFourSixTwoSixAgg.sol | 2 + test/common/FourSixTwoSixAggBase.t.sol | 67 ++++++++++----------- test/e2e/BalanceForwarderE2ETest.t.sol | 20 +++---- 6 files changed, 90 insertions(+), 95 deletions(-) diff --git a/foundry.toml b/foundry.toml index da8c522e..518361a2 100644 --- a/foundry.toml +++ b/foundry.toml @@ -4,7 +4,7 @@ out = "out" libs = ["lib"] test = 'test' optimizer = true -optimizer_runs = 20_000 +optimizer_runs = 1000 # solc = "0.8.0" gas_reports = ["*"] fs_permissions = [{ access = "read", path = "./"}] diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index a1b0c837..d9dc8055 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -44,18 +44,18 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 uint8 internal constant REENTRANCYLOCK__LOCKED = 2; // Roles - bytes32 public constant STRATEGY_MANAGER_ROLE = keccak256("STRATEGY_MANAGER_ROLE"); - bytes32 public constant STRATEGY_MANAGER_ROLE_ADMINROLE = keccak256("STRATEGY_MANAGER_ROLE_ADMINROLE"); - bytes32 public constant WITHDRAW_QUEUE_MANAGER_ROLE = keccak256("WITHDRAW_QUEUE_MANAGER_ROLE"); - bytes32 public constant WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE = keccak256("WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE"); - bytes32 public constant STRATEGY_ADDER_ROLE = keccak256("STRATEGY_ADDER_ROLE"); - bytes32 public constant STRATEGY_ADDER_ROLE_ADMINROLE = keccak256("STRATEGY_ADDER_ROLE_ADMINROLE"); - bytes32 public constant STRATEGY_REMOVER_ROLE = keccak256("STRATEGY_REMOVER_ROLE"); - bytes32 public constant STRATEGY_REMOVER_ROLE_ADMINROLE = keccak256("STRATEGY_REMOVER_ROLE_ADMINROLE"); - bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); - bytes32 public constant MANAGER_ROLE_ADMINROLE = keccak256("MANAGER_ROLE_ADMINROLE"); - bytes32 public constant REBALANCER_ROLE = keccak256("REBALANCER_ROLE"); - bytes32 public constant REBALANCER_ROLE_ADMINROLE = keccak256("REBALANCER_ROLE_ADMINROLE"); + bytes32 public constant STRATEGY_MANAGER = keccak256("STRATEGY_MANAGER"); + bytes32 public constant STRATEGY_MANAGER_ADMIN = keccak256("STRATEGY_MANAGER_ADMIN"); + bytes32 public constant WITHDRAW_QUEUE_MANAGER = keccak256("WITHDRAW_QUEUE_MANAGER"); + bytes32 public constant WITHDRAW_QUEUE_MANAGER_ADMIN = keccak256("WITHDRAW_QUEUE_MANAGER_ADMIN"); + bytes32 public constant STRATEGY_ADDER = keccak256("STRATEGY_ADDER"); + bytes32 public constant STRATEGY_ADDER_ADMIN = keccak256("STRATEGY_ADDER_ADMIN"); + bytes32 public constant STRATEGY_REMOVER = keccak256("STRATEGY_REMOVER"); + bytes32 public constant STRATEGY_REMOVER_ADMIN = keccak256("STRATEGY_REMOVER_ADMIN"); + bytes32 public constant MANAGER = keccak256("MANAGER"); + bytes32 public constant MANAGER_ADMIN = keccak256("MANAGER_ADMIN"); + bytes32 public constant REBALANCER = keccak256("REBALANCER"); + bytes32 public constant REBALANCER_ADMIN = keccak256("REBALANCER_ADMIN"); /// @dev The maximum performanceFee the vault can have is 50% uint256 internal constant MAX_PERFORMANCE_FEE = 0.5e18; @@ -161,16 +161,16 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 _grantRole(DEFAULT_ADMIN_ROLE, _msgSender()); // Setup role admins - _setRoleAdmin(STRATEGY_MANAGER_ROLE, STRATEGY_MANAGER_ROLE_ADMINROLE); - _setRoleAdmin(WITHDRAW_QUEUE_MANAGER_ROLE, WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE); - _setRoleAdmin(STRATEGY_ADDER_ROLE, STRATEGY_ADDER_ROLE_ADMINROLE); - _setRoleAdmin(STRATEGY_REMOVER_ROLE, STRATEGY_REMOVER_ROLE_ADMINROLE); - _setRoleAdmin(MANAGER_ROLE, MANAGER_ROLE_ADMINROLE); + _setRoleAdmin(STRATEGY_MANAGER, STRATEGY_MANAGER_ADMIN); + _setRoleAdmin(WITHDRAW_QUEUE_MANAGER, WITHDRAW_QUEUE_MANAGER_ADMIN); + _setRoleAdmin(STRATEGY_ADDER, STRATEGY_ADDER_ADMIN); + _setRoleAdmin(STRATEGY_REMOVER, STRATEGY_REMOVER_ADMIN); + _setRoleAdmin(MANAGER, MANAGER_ADMIN); } /// @notice Set performance fee recipient address /// @notice @param _newFeeRecipient Recipient address - function setFeeRecipient(address _newFeeRecipient) external onlyRole(MANAGER_ROLE) { + function setFeeRecipient(address _newFeeRecipient) external onlyRole(MANAGER) { if (_newFeeRecipient == feeRecipient) revert FeeRecipientAlreadySet(); emit SetFeeRecipient(feeRecipient, _newFeeRecipient); @@ -180,7 +180,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @notice Set performance fee (1e18 == 100%) /// @notice @param _newFee Fee rate - function setPerformanceFee(uint256 _newFee) external onlyRole(MANAGER_ROLE) { + function setPerformanceFee(uint256 _newFee) external onlyRole(MANAGER) { if (_newFee > MAX_PERFORMANCE_FEE) revert MaxPerformanceFeeExceeded(); if (feeRecipient == address(0)) revert FeeRecipientNotSet(); if (_newFee == performanceFee) revert PerformanceFeeAlreadySet(); @@ -192,7 +192,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @notice Opt in to strategy rewards /// @param _strategy Strategy address - function optInStrategyRewards(address _strategy) external onlyRole(MANAGER_ROLE) { + function optInStrategyRewards(address _strategy) external onlyRole(MANAGER) { if (!strategies[_strategy].active) revert InactiveStrategy(); IBalanceForwarder(_strategy).enableBalanceForwarder(); @@ -202,7 +202,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @notice Opt out of strategy rewards /// @param _strategy Strategy address - function optOutStrategyRewards(address _strategy) external onlyRole(MANAGER_ROLE) { + function optOutStrategyRewards(address _strategy) external onlyRole(MANAGER) { IBalanceForwarder(_strategy).disableBalanceForwarder(); emit OptOutStrategyRewards(_strategy); @@ -220,7 +220,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 address _reward, address _recipient, bool _forfeitRecentReward - ) external onlyRole(MANAGER_ROLE) { + ) external onlyRole(MANAGER) { address rewardStreams = IBalanceForwarder(_strategy).balanceTrackerAddress(); IRewardStreams(rewardStreams).claimReward(_rewarded, _reward, _recipient, _forfeitRecentReward); @@ -261,9 +261,9 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) external nonReentrant - onlyRole(REBALANCER_ROLE) + onlyRole(REBALANCER) { - _harvest(_strategy); + // _harvest(_strategy); Strategy memory strategyData = strategies[_strategy]; @@ -281,13 +281,13 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 } /// @notice Adjust a certain strategy's allocation points. - /// @dev Can only be called by an address that have the STRATEGY_MANAGER_ROLE + /// @dev Can only be called by an address that have the STRATEGY_MANAGER /// @param _strategy address of strategy /// @param _newPoints new strategy's points function adjustAllocationPoints(address _strategy, uint256 _newPoints) external nonReentrant - onlyRole(STRATEGY_MANAGER_ROLE) + onlyRole(STRATEGY_MANAGER) { Strategy memory strategyDataCache = strategies[_strategy]; @@ -305,7 +305,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @dev By default, cap is set to 0, not activated. /// @param _strategy Strategy address. /// @param _cap Cap amount - function setStrategyCap(address _strategy, uint256 _cap) external nonReentrant onlyRole(STRATEGY_MANAGER_ROLE) { + function setStrategyCap(address _strategy, uint256 _cap) external nonReentrant onlyRole(STRATEGY_MANAGER) { Strategy memory strategyDataCache = strategies[_strategy]; if (!strategyDataCache.active) { @@ -318,13 +318,13 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 } /// @notice Swap two strategies indexes in the withdrawal queue. - /// @dev Can only be called by an address that have the WITHDRAW_QUEUE_MANAGER_ROLE. + /// @dev Can only be called by an address that have the WITHDRAW_QUEUE_MANAGER. /// @param _index1 index of first strategy /// @param _index2 index of second strategy function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) external nonReentrant - onlyRole(WITHDRAW_QUEUE_MANAGER_ROLE) + onlyRole(WITHDRAW_QUEUE_MANAGER) { uint256 length = withdrawalQueue.length; if (_index1 >= length || _index2 >= length) { @@ -341,14 +341,10 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 } /// @notice Add new strategy with it's allocation points. - /// @dev Can only be called by an address that have STRATEGY_ADDER_ROLE. + /// @dev Can only be called by an address that have STRATEGY_ADDER. /// @param _strategy Address of the strategy /// @param _allocationPoints Strategy's allocation points - function addStrategy(address _strategy, uint256 _allocationPoints) - external - nonReentrant - onlyRole(STRATEGY_ADDER_ROLE) - { + function addStrategy(address _strategy, uint256 _allocationPoints) external nonReentrant onlyRole(STRATEGY_ADDER) { if (IERC4626(_strategy).asset() != asset()) { revert InvalidStrategyAsset(); } @@ -370,9 +366,9 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @notice Remove strategy and set its allocation points to zero. /// @dev This function does not pull funds, `harvest()` needs to be called to withdraw - /// @dev Can only be called by an address that have the STRATEGY_REMOVER_ROLE + /// @dev Can only be called by an address that have the STRATEGY_REMOVER /// @param _strategy Address of the strategy - function removeStrategy(address _strategy) external nonReentrant onlyRole(STRATEGY_REMOVER_ROLE) { + function removeStrategy(address _strategy) external nonReentrant onlyRole(STRATEGY_REMOVER) { if (_strategy == address(0)) revert CanNotRemoveCashReserve(); Strategy storage strategyStorage = strategies[_strategy]; @@ -444,14 +440,9 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @param to The recipient of the transfer. /// @param amount The amount shares to transfer. /// @return A boolean indicating whether the transfer was successful. - function transfer(address to, uint256 amount) - public - override (ERC20, IERC20) - nonReentrant - returns (bool) - { + function transfer(address to, uint256 amount) public override (ERC20, IERC20) nonReentrant returns (bool) { super.transfer(to, amount); - + _requireAccountStatusCheck(_msgSender()); return true; @@ -519,7 +510,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @dev This funtion should be overriden to implement access control. /// @param _hookTarget Hooks contract. /// @param _hookedFns Hooked functions. - function setHooksConfig(address _hookTarget, uint32 _hookedFns) public override onlyRole(MANAGER_ROLE) { + function setHooksConfig(address _hookTarget, uint32 _hookedFns) public override onlyRole(MANAGER) { super.setHooksConfig(_hookTarget, _hookedFns); } diff --git a/src/Rebalancer.sol b/src/Rebalancer.sol index 479892ef..42d99c85 100644 --- a/src/Rebalancer.sol +++ b/src/Rebalancer.sol @@ -28,6 +28,8 @@ contract Rebalancer { // _callHookTarget(REBALANCE, _msgSender()); + IFourSixTwoSixAgg(_curatedVault).harvest(_strategy); + IFourSixTwoSixAgg.Strategy memory strategyData = IFourSixTwoSixAgg(_curatedVault).getStrategy(_strategy); // no rebalance if strategy have an allocated amount greater than cap @@ -41,16 +43,17 @@ contract Rebalancer { if ((strategyData.cap > 0) && (targetAllocation > strategyData.cap)) targetAllocation = strategyData.cap; uint256 amountToRebalance; + bool isDeposit; if (strategyData.allocated > targetAllocation) { // Withdraw amountToRebalance = strategyData.allocated - targetAllocation; - uint256 maxWithdraw = IERC4626(_strategy).maxWithdraw(address(this)); + uint256 maxWithdraw = IERC4626(_strategy).maxWithdraw(_curatedVault); if (amountToRebalance > maxWithdraw) { amountToRebalance = maxWithdraw; } - IFourSixTwoSixAgg(_curatedVault).rebalance(_strategy, amountToRebalance, false); + isDeposit = false; } else if (strategyData.allocated < targetAllocation) { // Deposit uint256 targetCash = totalAssetsAllocatableCache @@ -65,7 +68,7 @@ contract Rebalancer { amountToRebalance = cashAvailable; } - uint256 maxDeposit = IERC4626(_strategy).maxDeposit(address(this)); + uint256 maxDeposit = IERC4626(_strategy).maxDeposit(_curatedVault); if (amountToRebalance > maxDeposit) { amountToRebalance = maxDeposit; } @@ -74,9 +77,11 @@ contract Rebalancer { return; // No cash to deposit } - IFourSixTwoSixAgg(_curatedVault).rebalance(_strategy, amountToRebalance, true); + isDeposit = true; } + IFourSixTwoSixAgg(_curatedVault).rebalance(_strategy, amountToRebalance, isDeposit); + // emit Rebalance(_strategy, strategyData.allocated, targetAllocation, amountToRebalance) } diff --git a/src/interface/IFourSixTwoSixAgg.sol b/src/interface/IFourSixTwoSixAgg.sol index 68dcc390..b8323236 100644 --- a/src/interface/IFourSixTwoSixAgg.sol +++ b/src/interface/IFourSixTwoSixAgg.sol @@ -15,6 +15,8 @@ interface IFourSixTwoSixAgg { } function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) external; + function gulp() external; + function harvest(address strategy) external; function getStrategy(address _strategy) external view returns (Strategy memory); function totalAllocationPoints() external view returns (uint256); diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol index 223f7ff9..34e98c5a 100644 --- a/test/common/FourSixTwoSixAggBase.t.sol +++ b/test/common/FourSixTwoSixAggBase.t.sol @@ -39,22 +39,22 @@ contract FourSixTwoSixAggBase is EVaultTestBase { ); // grant admin roles to deployer - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE_ADMINROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMINROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMINROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER_ROLE_ADMINROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.REBALANCER_ROLE_ADMINROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ADMIN(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ADMIN(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ADMIN(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER_ADMIN(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.REBALANCER_ADMIN(), deployer); // grant roles to manager - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER_ROLE(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER(), manager); // grant rebalancing role - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.REBALANCER_ROLE(), address(rebalancer)); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.REBALANCER(), address(rebalancer)); vm.stopPrank(); } @@ -67,36 +67,33 @@ contract FourSixTwoSixAggBase is EVaultTestBase { assertEq(cashReserve.active, true); assertEq( - fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE()), - fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE_ADMINROLE() + fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_MANAGER()), + fourSixTwoSixAgg.STRATEGY_MANAGER_ADMIN() ); assertEq( - fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE()), - fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE() + fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER()), + fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ADMIN() ); assertEq( - fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE()), - fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMINROLE() + fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_ADDER()), fourSixTwoSixAgg.STRATEGY_ADDER_ADMIN() ); assertEq( - fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE()), - fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMINROLE() + fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_REMOVER()), + fourSixTwoSixAgg.STRATEGY_REMOVER_ADMIN() ); - assertEq( - fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.MANAGER_ROLE()), fourSixTwoSixAgg.MANAGER_ROLE_ADMINROLE() - ); - - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE_ADMINROLE(), deployer)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE(), deployer)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMINROLE(), deployer)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMINROLE(), deployer)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.MANAGER_ROLE_ADMINROLE(), deployer)); - - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE(), manager)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE(), manager)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.MANAGER_ROLE(), manager)); + assertEq(fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.MANAGER()), fourSixTwoSixAgg.MANAGER_ADMIN()); + + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ADMIN(), deployer)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER_ADMIN(), deployer)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ADMIN(), deployer)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.MANAGER_ADMIN(), deployer)); + + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_MANAGER(), manager)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER(), manager)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER(), manager)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER(), manager)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.MANAGER(), manager)); } function _addStrategy(address from, address strategy, uint256 allocationPoints) internal { diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index b327e1b4..75873f97 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -35,18 +35,18 @@ contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { ); // grant admin roles to deployer - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE_ADMINROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMINROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMINROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER_ROLE_ADMINROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ADMIN(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ADMIN(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ADMIN(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER_ADMIN(), deployer); // grant roles to manager - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER_ROLE(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER(), manager); vm.stopPrank(); uint256 initialStrategyAllocationPoints = 500e18; From fdd19a29779a02ebf6ec7d6d75ee6ee3c407fd35 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 11 Jun 2024 16:47:46 +0300 Subject: [PATCH 4/8] chore: add bytecode size checks to CI --- .github/workflows/test.yml | 2 +- src/FourSixTwoSixAgg.sol | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a04ee8bc..16f20b19 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: version: nightly - name: Run foundry build - run: forge build + run: forge build --sizes - name: Run foundry fmt check run: forge fmt --check diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index d9dc8055..c63322ea 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -2,18 +2,18 @@ pragma solidity ^0.8.0; // external dep -// internal dep -import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; import {Context} from "@openzeppelin/utils/Context.sol"; import {ERC20, IERC20} from "@openzeppelin/token/ERC20/ERC20.sol"; 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 {SafeCast} from "@openzeppelin/utils/math/SafeCast.sol"; import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; -import {BalanceForwarder, IBalanceForwarder} from "./BalanceForwarder.sol"; import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol"; -import {SafeCast} from "@openzeppelin/utils/math/SafeCast.sol"; +// internal dep import {Hooks} from "./Hooks.sol"; +import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; +import {BalanceForwarder, IBalanceForwarder} from "./BalanceForwarder.sol"; /// @dev Do NOT use with fee on transfer tokens /// @dev Do NOT use with rebasing tokens From 60356e3f6fedde80cf9e16c94de2d8064ceaa751 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 11 Jun 2024 16:53:52 +0300 Subject: [PATCH 5/8] move event --- src/FourSixTwoSixAgg.sol | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index c63322ea..4a62cf9c 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -91,9 +91,6 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 event SetPerformanceFee(uint256 oldFee, uint256 newFee); event OptInStrategyRewards(address indexed strategy); event OptOutStrategyRewards(address indexed strategy); - event Rebalance( - address indexed strategy, uint256 currentAllocation, uint256 targetAllocation, uint256 amountToRebalance - ); event Gulp(uint256 interestLeft, uint256 interestSmearEnd); event Harvest(address indexed strategy, uint256 strategyBalanceAmount, uint256 strategyAllocatedAmount); event AdjustAllocationPoints(address indexed strategy, uint256 oldPoints, uint256 newPoints); @@ -263,8 +260,6 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 nonReentrant onlyRole(REBALANCER) { - // _harvest(_strategy); - Strategy memory strategyData = strategies[_strategy]; if (_isDeposit) { From 88daa438f8f5b21b967abb74dd816f2ee8f296c7 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 12 Jun 2024 11:11:50 +0300 Subject: [PATCH 6/8] remove rebalance hook --- src/Hooks.sol | 7 +++---- src/Rebalancer.sol | 37 ++++++++++++++++++++----------------- test/e2e/HooksE2ETest.t.sol | 6 +++--- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/Hooks.sol b/src/Hooks.sol index 17206686..c107df23 100644 --- a/src/Hooks.sol +++ b/src/Hooks.sol @@ -14,11 +14,10 @@ abstract contract Hooks { uint32 public constant DEPOSIT = 1 << 0; uint32 public constant WITHDRAW = 1 << 1; - uint32 public constant REBALANCE = 1 << 2; - uint32 public constant ADD_STRATEGY = 1 << 3; - uint32 public constant REMOVE_STRATEGY = 1 << 4; + uint32 public constant ADD_STRATEGY = 1 << 2; + uint32 public constant REMOVE_STRATEGY = 1 << 3; - uint32 constant ACTIONS_COUNTER = 1 << 5; + uint32 constant ACTIONS_COUNTER = 1 << 4; /// @dev Contract with hooks implementation address public hookTarget; diff --git a/src/Rebalancer.sol b/src/Rebalancer.sol index 42d99c85..6175a019 100644 --- a/src/Rebalancer.sol +++ b/src/Rebalancer.sol @@ -5,29 +5,40 @@ import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; import {IERC4626} from "@openzeppelin/token/ERC20/extensions/ERC4626.sol"; contract Rebalancer { - /// @notice Rebalance strategy allocation. - /// @dev This function will first harvest yield, gulps and update interest. - /// @dev If current allocation is greater than target allocation, the aggregator will withdraw the excess assets. - /// If current allocation is less than target allocation, the aggregator will: - /// - Try to deposit the delta, if the cash is not sufficient, deposit all the available cash - /// - If all the available cash is greater than the max deposit, deposit the max deposit + event Rebalance( + address indexed curatedVault, + address indexed strategy, + uint256 currentAllocation, + uint256 targetAllocation, + uint256 amountToRebalance + ); + + /// @notice Rebalance strategy allocation for a specific curated vault. + /// @param _curatedVault Curated vault address. + /// @param _strategy Strategy address. function rebalance(address _curatedVault, address _strategy) external { _rebalance(_curatedVault, _strategy); } + /// @notice Rebalance strategies allocation for a specific curated vault. + /// @param _curatedVault Curated vault address. + /// @param strategies Strategies addresses. function rebalanceMultipleStrategies(address _curatedVault, address[] calldata strategies) external { for (uint256 i; i < strategies.length; ++i) { _rebalance(_curatedVault, strategies[i]); } } + /// @dev This function will first harvest yield, gulps and update interest. + /// @dev If current allocation is greater than target allocation, the aggregator will withdraw the excess assets. + /// If current allocation is less than target allocation, the aggregator will: + /// - Try to deposit the delta, if the cash is not sufficient, deposit all the available cash + /// - If all the available cash is greater than the max deposit, deposit the max deposit function _rebalance(address _curatedVault, address _strategy) private { if (_strategy == address(0)) { return; //nothing to rebalance as that's the cash reserve } - // _callHookTarget(REBALANCE, _msgSender()); - IFourSixTwoSixAgg(_curatedVault).harvest(_strategy); IFourSixTwoSixAgg.Strategy memory strategyData = IFourSixTwoSixAgg(_curatedVault).getStrategy(_strategy); @@ -82,14 +93,6 @@ contract Rebalancer { IFourSixTwoSixAgg(_curatedVault).rebalance(_strategy, amountToRebalance, isDeposit); - // emit Rebalance(_strategy, strategyData.allocated, targetAllocation, amountToRebalance) + emit Rebalance(_curatedVault, _strategy, strategyData.allocated, targetAllocation, amountToRebalance); } - - // /// @notice Rebalance strategy's allocation. - // /// @param _strategy Address of strategy to rebalance. - // function rebalance(address _strategy) external { - // _rebalance(_strategy); - - // _gulp(); - // } } diff --git a/test/e2e/HooksE2ETest.t.sol b/test/e2e/HooksE2ETest.t.sol index 62176738..efff9207 100644 --- a/test/e2e/HooksE2ETest.t.sol +++ b/test/e2e/HooksE2ETest.t.sol @@ -27,7 +27,7 @@ contract HooksE2ETest is FourSixTwoSixAggBase { function testSetHooksConfig() public { uint32 expectedHookedFns = fourSixTwoSixAgg.DEPOSIT() | fourSixTwoSixAgg.WITHDRAW() - | fourSixTwoSixAgg.REBALANCE() | fourSixTwoSixAgg.ADD_STRATEGY() | fourSixTwoSixAgg.REMOVE_STRATEGY(); + | fourSixTwoSixAgg.ADD_STRATEGY() | fourSixTwoSixAgg.REMOVE_STRATEGY(); vm.startPrank(manager); address hooksContract = address(new HooksContract()); @@ -42,7 +42,7 @@ contract HooksE2ETest is FourSixTwoSixAggBase { function testSetHooksConfigWithAddressZero() public { uint32 expectedHookedFns = fourSixTwoSixAgg.DEPOSIT() | fourSixTwoSixAgg.WITHDRAW() - | fourSixTwoSixAgg.REBALANCE() | fourSixTwoSixAgg.ADD_STRATEGY() | fourSixTwoSixAgg.REMOVE_STRATEGY(); + | fourSixTwoSixAgg.ADD_STRATEGY() | fourSixTwoSixAgg.REMOVE_STRATEGY(); vm.startPrank(manager); vm.expectRevert(Hooks.InvalidHooksTarget.selector); @@ -52,7 +52,7 @@ contract HooksE2ETest is FourSixTwoSixAggBase { function testSetHooksConfigWithNotHooksContract() public { uint32 expectedHookedFns = fourSixTwoSixAgg.DEPOSIT() | fourSixTwoSixAgg.WITHDRAW() - | fourSixTwoSixAgg.REBALANCE() | fourSixTwoSixAgg.ADD_STRATEGY() | fourSixTwoSixAgg.REMOVE_STRATEGY(); + | fourSixTwoSixAgg.ADD_STRATEGY() | fourSixTwoSixAgg.REMOVE_STRATEGY(); vm.startPrank(manager); address hooksContract = address(new NotHooksContract()); From 32e39cf0948cce5a673d62a3a88807cdf0c1e3b2 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 12 Jun 2024 11:44:06 +0300 Subject: [PATCH 7/8] refactor --- src/Rebalancer.sol | 13 ++----- ...positRebalanceHarvestWithdrawE2ETest.t.sol | 18 ++++++---- test/e2e/PerformanceFeeE2ETest.t.sol | 4 ++- test/e2e/StrategyCapE2ETest.t.sol | 12 +++++-- test/unit/GulpTest.t.sol | 4 ++- test/unit/HarvestTest.t.sol | 4 ++- test/unit/RebalanceTest.t.sol | 34 ++++++++++++++----- 7 files changed, 58 insertions(+), 31 deletions(-) diff --git a/src/Rebalancer.sol b/src/Rebalancer.sol index 6175a019..f9fdec6c 100644 --- a/src/Rebalancer.sol +++ b/src/Rebalancer.sol @@ -5,7 +5,7 @@ import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; import {IERC4626} from "@openzeppelin/token/ERC20/extensions/ERC4626.sol"; contract Rebalancer { - event Rebalance( + event ExecuteRebalance( address indexed curatedVault, address indexed strategy, uint256 currentAllocation, @@ -13,17 +13,10 @@ contract Rebalancer { uint256 amountToRebalance ); - /// @notice Rebalance strategy allocation for a specific curated vault. - /// @param _curatedVault Curated vault address. - /// @param _strategy Strategy address. - function rebalance(address _curatedVault, address _strategy) external { - _rebalance(_curatedVault, _strategy); - } - /// @notice Rebalance strategies allocation for a specific curated vault. /// @param _curatedVault Curated vault address. /// @param strategies Strategies addresses. - function rebalanceMultipleStrategies(address _curatedVault, address[] calldata strategies) external { + function executeRebalance(address _curatedVault, address[] calldata strategies) external { for (uint256 i; i < strategies.length; ++i) { _rebalance(_curatedVault, strategies[i]); } @@ -93,6 +86,6 @@ contract Rebalancer { IFourSixTwoSixAgg(_curatedVault).rebalance(_strategy, amountToRebalance, isDeposit); - emit Rebalance(_curatedVault, _strategy, strategyData.allocated, targetAllocation, amountToRebalance); + emit ExecuteRebalance(_curatedVault, _strategy, strategyData.allocated, targetAllocation, amountToRebalance); } } diff --git a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol index 3bd66ae9..8aae2567 100644 --- a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol @@ -55,7 +55,9 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); @@ -134,7 +136,9 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); @@ -233,7 +237,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - rebalancer.rebalanceMultipleStrategies(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedeTSTStrategyCash); @@ -319,7 +323,9 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); @@ -421,7 +427,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - rebalancer.rebalanceMultipleStrategies(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedeTSTStrategyCash); @@ -552,7 +558,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - rebalancer.rebalanceMultipleStrategies(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedeTSTStrategyCash); diff --git a/test/e2e/PerformanceFeeE2ETest.t.sol b/test/e2e/PerformanceFeeE2ETest.t.sol index ece89ae4..f8cec8a9 100644 --- a/test/e2e/PerformanceFeeE2ETest.t.sol +++ b/test/e2e/PerformanceFeeE2ETest.t.sol @@ -80,7 +80,9 @@ contract PerformanceFeeE2ETest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); diff --git a/test/e2e/StrategyCapE2ETest.t.sol b/test/e2e/StrategyCapE2ETest.t.sol index 1a9172b6..e0235ecd 100644 --- a/test/e2e/StrategyCapE2ETest.t.sol +++ b/test/e2e/StrategyCapE2ETest.t.sol @@ -80,7 +80,9 @@ contract StrategyCapE2ETest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); @@ -98,7 +100,9 @@ contract StrategyCapE2ETest is FourSixTwoSixAggBase { uint256 strategyAllocatedBefore = (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated; - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); vm.stopPrank(); assertEq(strategyAllocatedBefore, (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated); @@ -141,7 +145,9 @@ contract StrategyCapE2ETest is FourSixTwoSixAggBase { fourSixTwoSixAgg.setStrategyCap(address(eTST), cap); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), cap); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), cap); diff --git a/test/unit/GulpTest.t.sol b/test/unit/GulpTest.t.sol index 2f826be4..a31f4b92 100644 --- a/test/unit/GulpTest.t.sol +++ b/test/unit/GulpTest.t.sol @@ -44,7 +44,9 @@ contract GulpTest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); diff --git a/test/unit/HarvestTest.t.sol b/test/unit/HarvestTest.t.sol index 52c6128e..7ee54b8d 100644 --- a/test/unit/HarvestTest.t.sol +++ b/test/unit/HarvestTest.t.sol @@ -44,7 +44,9 @@ contract HarvestTest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); diff --git a/test/unit/RebalanceTest.t.sol b/test/unit/RebalanceTest.t.sol index a5fd0d93..5342e9b0 100644 --- a/test/unit/RebalanceTest.t.sol +++ b/test/unit/RebalanceTest.t.sol @@ -54,7 +54,9 @@ contract RebalanceTest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); @@ -100,7 +102,9 @@ contract RebalanceTest is FourSixTwoSixAggBase { ); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), eTSTMaxDeposit); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), eTSTMaxDeposit); @@ -131,7 +135,9 @@ contract RebalanceTest is FourSixTwoSixAggBase { // rebalance into first strategy vm.warp(block.timestamp + 86400); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); // create new strategy & add it IEVault eTSTsecondary; @@ -161,7 +167,9 @@ contract RebalanceTest is FourSixTwoSixAggBase { uint256 expectedStrategyCash = currentCash - targetCash; vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTSTsecondary)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTSTsecondary); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); // assertEq(fourSixTwoSixAgg.totalAllocated(), eTSTsecondaryMaxDeposit); assertEq( @@ -208,7 +216,9 @@ contract RebalanceTest is FourSixTwoSixAggBase { ); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), strategyBefore.allocated); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); @@ -239,7 +249,9 @@ contract RebalanceTest is FourSixTwoSixAggBase { // rebalance into strategy vm.warp(block.timestamp + 86400); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); // decrease allocation points uint256 newAllocationPoints = 300e18; @@ -256,7 +268,8 @@ contract RebalanceTest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); @@ -291,7 +304,9 @@ contract RebalanceTest is FourSixTwoSixAggBase { // rebalance into strategy vm.warp(block.timestamp + 86400); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); // decrease allocation points uint256 newAllocationPoints = 300e18; @@ -314,7 +329,8 @@ contract RebalanceTest is FourSixTwoSixAggBase { ); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); // assertEq(fourSixTwoSixAgg.totalAllocated(), strategyBefore.allocated - eTSTMaxWithdraw); // assertEq( From a436765d2bebc53064e11249b501a430bbab70c0 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 12 Jun 2024 12:15:52 +0300 Subject: [PATCH 8/8] add event --- src/FourSixTwoSixAgg.sol | 3 +++ src/Rebalancer.sol | 10 ++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 4a62cf9c..179af3b1 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -99,6 +99,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 event RemoveStrategy(address indexed _strategy); event AccruePerformanceFee(address indexed feeRecipient, uint256 performanceFee, uint256 yield, uint256 feeShares); event SetStrategyCap(address indexed strategy, uint256 cap); + event Rebalance(address indexed strategy, uint256 _amountToRebalance, bool _isDeposit); /// @dev Non reentrancy modifier for interest rate updates modifier nonReentrant() { @@ -273,6 +274,8 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 strategies[_strategy].allocated = (strategyData.allocated - _amountToRebalance).toUint120(); totalAllocated -= _amountToRebalance; } + + emit Rebalance(_strategy, _amountToRebalance, _isDeposit); } /// @notice Adjust a certain strategy's allocation points. diff --git a/src/Rebalancer.sol b/src/Rebalancer.sol index f9fdec6c..2be3806f 100644 --- a/src/Rebalancer.sol +++ b/src/Rebalancer.sol @@ -15,10 +15,10 @@ contract Rebalancer { /// @notice Rebalance strategies allocation for a specific curated vault. /// @param _curatedVault Curated vault address. - /// @param strategies Strategies addresses. - function executeRebalance(address _curatedVault, address[] calldata strategies) external { - for (uint256 i; i < strategies.length; ++i) { - _rebalance(_curatedVault, strategies[i]); + /// @param _strategies Strategies addresses. + function executeRebalance(address _curatedVault, address[] calldata _strategies) external { + for (uint256 i; i < _strategies.length; ++i) { + _rebalance(_curatedVault, _strategies[i]); } } @@ -27,6 +27,8 @@ contract Rebalancer { /// If current allocation is less than target allocation, the aggregator will: /// - Try to deposit the delta, if the cash is not sufficient, deposit all the available cash /// - If all the available cash is greater than the max deposit, deposit the max deposit + /// @param _curatedVault Curated vault address. + /// @param _strategy Strategy address. function _rebalance(address _curatedVault, address _strategy) private { if (_strategy == address(0)) { return; //nothing to rebalance as that's the cash reserve