From e5f9f3a48a6aa5f1a5e31dce847473a9d11f2869 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 7 Jun 2024 15:41:50 +0100 Subject: [PATCH 1/4] feat: init cap impl --- src/FourSixTwoSixAgg.sol | 61 +++++++++++++++++--------- test/common/FourSixTwoSixAggBase.t.sol | 42 +++++++++--------- test/e2e/BalanceForwarderE2ETest.t.sol | 14 +++--- 3 files changed, 69 insertions(+), 48 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 66ba6442..df056e06 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -40,17 +40,17 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn uint8 internal constant REENTRANCYLOCK__LOCKED = 2; // Roles - bytes32 public constant ALLOCATION_ADJUSTER_ROLE = keccak256("ALLOCATION_ADJUSTER_ROLE"); - bytes32 public constant ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE = keccak256("ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE"); - bytes32 public constant WITHDRAW_QUEUE_REORDERER_ROLE = keccak256("WITHDRAW_QUEUE_REORDERER_ROLE"); - bytes32 public constant WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE = - keccak256("WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE"); + 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_ADMIN_ROLE = keccak256("STRATEGY_ADDER_ROLE_ADMIN_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_ADMIN_ROLE = keccak256("STRATEGY_REMOVER_ROLE_ADMIN_ROLE"); + bytes32 public constant STRATEGY_REMOVER_ROLE_ADMINROLE = keccak256("STRATEGY_REMOVER_ROLE_ADMINROLE"); bytes32 public constant TREASURY_MANAGER_ROLE = keccak256("TREASURY_MANAGER_ROLE"); - bytes32 public constant TREASURY_MANAGER_ROLE_ADMIN_ROLE = keccak256("TREASURY_MANAGER_ROLE_ADMIN_ROLE"); + bytes32 public constant TREASURY_MANAGER_ROLE_ADMINROLE = keccak256("TREASURY_MANAGER_ROLE_ADMINROLE"); /// @dev The maximum performanceFee the vault can have is 50% uint256 internal constant MAX_PERFORMANCE_FEE = 0.5e18; @@ -86,10 +86,12 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// 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); @@ -148,7 +150,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn if (_initialCashAllocationPoints == 0) revert InitialAllocationPointsZero(); strategies[address(0)] = - Strategy({allocated: 0, allocationPoints: _initialCashAllocationPoints.toUint120(), active: true}); + Strategy({allocated: 0, allocationPoints: _initialCashAllocationPoints.toUint120(), active: true, cap: 0}); uint256 cachedTotalAllocationPoints = _initialCashAllocationPoints; @@ -160,7 +162,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn strategies[_initialStrategies[i]] = Strategy({ allocated: 0, allocationPoints: _initialStrategiesAllocationPoints[i].toUint120(), - active: true + active: true, + cap: 0 }); cachedTotalAllocationPoints += _initialStrategiesAllocationPoints[i]; @@ -172,11 +175,11 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn _grantRole(DEFAULT_ADMIN_ROLE, _msgSender()); // Setup role admins - _setRoleAdmin(ALLOCATION_ADJUSTER_ROLE, ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE); - _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(TREASURY_MANAGER_ROLE, TREASURY_MANAGER_ROLE_ADMIN_ROLE); + _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(TREASURY_MANAGER_ROLE, TREASURY_MANAGER_ROLE_ADMINROLE); } /// @notice Set performance fee recipient address @@ -288,13 +291,13 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn } /// @notice Adjust a certain strategy's allocation points. - /// @dev Can only be called by an address that have the ALLOCATION_ADJUSTER_ROLE + /// @dev Can only be called by an address that have the STRATEGY_MANAGER_ROLE /// @param _strategy address of strategy /// @param _newPoints new strategy's points function adjustAllocationPoints(address _strategy, uint256 _newPoints) external nonReentrant - onlyRole(ALLOCATION_ADJUSTER_ROLE) + onlyRole(STRATEGY_MANAGER_ROLE) { Strategy memory strategyDataCache = strategies[_strategy]; @@ -308,14 +311,32 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn emit AdjustAllocationPoints(_strategy, strategyDataCache.allocationPoints, _newPoints); } + event SetStrategyCap(address indexed strategy, uint256 cap); + + function setStrategyCap(address _strategy, uint256 _cap) + external + nonReentrant + onlyRole(STRATEGY_MANAGER_ROLE) + { + Strategy memory strategyDataCache = strategies[_strategy]; + + if (!strategyDataCache.active) { + revert InactiveStrategy(); + } + + strategies[_strategy].cap = _cap.toUint120(); + + emit SetStrategyCap(_strategy, _cap); + } + /// @notice Swap two strategies indexes in the withdrawal queue. - /// @dev Can only be called by an address that have the WITHDRAW_QUEUE_REORDERER_ROLE. + /// @dev Can only be called by an address that have the WITHDRAW_QUEUE_MANAGER_ROLE. /// @param _index1 index of first strategy /// @param _index2 index of second strategy function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) external nonReentrant - onlyRole(WITHDRAW_QUEUE_REORDERER_ROLE) + onlyRole(WITHDRAW_QUEUE_MANAGER_ROLE) { uint256 length = withdrawalQueue.length; if (_index1 >= length || _index2 >= length) { @@ -348,7 +369,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn revert StrategyAlreadyExist(); } - strategies[_strategy] = Strategy({allocated: 0, allocationPoints: _allocationPoints.toUint120(), active: true}); + strategies[_strategy] = Strategy({allocated: 0, allocationPoints: _allocationPoints.toUint120(), active: true, cap: 0}); totalAllocationPoints += _allocationPoints; withdrawalQueue.push(_strategy); diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol index 389a2528..5c2dc191 100644 --- a/test/common/FourSixTwoSixAggBase.t.sol +++ b/test/common/FourSixTwoSixAggBase.t.sol @@ -34,15 +34,15 @@ contract FourSixTwoSixAggBase is EVaultTestBase { ); // grant admin roles to deployer - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE(), deployer); - 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.TREASURY_MANAGER_ROLE_ADMIN_ROLE(), 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.TREASURY_MANAGER_ROLE_ADMINROLE(), deployer); // grant roles to manager - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE(), 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.TREASURY_MANAGER_ROLE(), manager); @@ -58,34 +58,34 @@ contract FourSixTwoSixAggBase is EVaultTestBase { assertEq(cashReserve.active, true); assertEq( - fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE()), - fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE() + fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE()), + fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE_ADMINROLE() ); assertEq( - fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE()), - fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE() + fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE()), + fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE() ); assertEq( fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE()), - fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMIN_ROLE() + fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMINROLE() ); assertEq( fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE()), - fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMIN_ROLE() + fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMINROLE() ); assertEq( fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE()), - fourSixTwoSixAgg.TREASURY_MANAGER_ROLE_ADMIN_ROLE() + fourSixTwoSixAgg.TREASURY_MANAGER_ROLE_ADMINROLE() ); - 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.TREASURY_MANAGER_ROLE_ADMIN_ROLE(), deployer)); + 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.TREASURY_MANAGER_ROLE_ADMINROLE(), deployer)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE(), manager)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE(), manager)); + 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.TREASURY_MANAGER_ROLE(), manager)); diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 746ae0b3..33927d78 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -35,15 +35,15 @@ contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { ); // grant admin roles to deployer - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE(), deployer); - 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.TREASURY_MANAGER_ROLE_ADMIN_ROLE(), 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.TREASURY_MANAGER_ROLE_ADMINROLE(), deployer); // grant roles to manager - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE(), 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.TREASURY_MANAGER_ROLE(), manager); From e171136b4fb66af8aa5ce336eb0dcc1047189297 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 10 Jun 2024 10:33:06 +0300 Subject: [PATCH 2/4] add tests --- src/FourSixTwoSixAgg.sol | 32 ++++++++++----------- test/e2e/StrategyCapE2ETest.t.sol | 46 +++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 16 deletions(-) create mode 100644 test/e2e/StrategyCapE2ETest.t.sol diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index df056e06..1b9bc81f 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -43,8 +43,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn 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 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"); @@ -313,11 +312,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn event SetStrategyCap(address indexed strategy, uint256 cap); - function setStrategyCap(address _strategy, uint256 _cap) - external - nonReentrant - onlyRole(STRATEGY_MANAGER_ROLE) - { + function setStrategyCap(address _strategy, uint256 _cap) external nonReentrant onlyRole(STRATEGY_MANAGER_ROLE) { Strategy memory strategyDataCache = strategies[_strategy]; if (!strategyDataCache.active) { @@ -369,7 +364,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn revert StrategyAlreadyExist(); } - strategies[_strategy] = Strategy({allocated: 0, allocationPoints: _allocationPoints.toUint120(), active: true, cap: 0}); + strategies[_strategy] = + Strategy({allocated: 0, allocationPoints: _allocationPoints.toUint120(), active: true, cap: 0}); totalAllocationPoints += _allocationPoints; withdrawalQueue.push(_strategy); @@ -645,16 +641,20 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn 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; - uint256 currentAllocation = strategyData.allocated; + + if ((strategyData.cap > 0) && (targetAllocation > strategyData.cap)) targetAllocation = strategyData.cap; uint256 amountToRebalance; - if (currentAllocation > targetAllocation) { + if (strategyData.allocated > targetAllocation) { // Withdraw - amountToRebalance = currentAllocation - targetAllocation; + amountToRebalance = strategyData.allocated - targetAllocation; uint256 maxWithdraw = IERC4626(_strategy).maxWithdraw(address(this)); if (amountToRebalance > maxWithdraw) { @@ -662,9 +662,9 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn } IERC4626(_strategy).withdraw(amountToRebalance, address(this), address(this)); - strategies[_strategy].allocated = (currentAllocation - amountToRebalance).toUint120(); + strategies[_strategy].allocated = (strategyData.allocated - amountToRebalance).toUint120(); totalAllocated -= amountToRebalance; - } else if (currentAllocation < targetAllocation) { + } else if (strategyData.allocated < targetAllocation) { // Deposit uint256 targetCash = totalAssetsAllocatableCache * strategies[address(0)].allocationPoints / totalAllocationPointsCache; @@ -673,7 +673,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn // Calculate available cash to put in strategies uint256 cashAvailable = (currentCash > targetCash) ? currentCash - targetCash : 0; - amountToRebalance = targetAllocation - currentAllocation; + amountToRebalance = targetAllocation - strategyData.allocated; if (amountToRebalance > cashAvailable) { amountToRebalance = cashAvailable; } @@ -690,11 +690,11 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn // Do required approval (safely) and deposit IERC20(asset()).safeApprove(_strategy, amountToRebalance); IERC4626(_strategy).deposit(amountToRebalance, address(this)); - strategies[_strategy].allocated = uint120(currentAllocation + amountToRebalance); + strategies[_strategy].allocated = uint120(strategyData.allocated + amountToRebalance); totalAllocated += amountToRebalance; } - emit Rebalance(_strategy, currentAllocation, targetAllocation, amountToRebalance); + emit Rebalance(_strategy, strategyData.allocated, targetAllocation, amountToRebalance); } function _harvest(address _strategy) internal { diff --git a/test/e2e/StrategyCapE2ETest.t.sol b/test/e2e/StrategyCapE2ETest.t.sol new file mode 100644 index 00000000..86d7d6d6 --- /dev/null +++ b/test/e2e/StrategyCapE2ETest.t.sol @@ -0,0 +1,46 @@ +// 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"; + +contract StrategyCapE2ETest 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 testSetCap() public { + uint256 cap = 1000000e18; + + assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).cap, 0); + + vm.prank(manager); + fourSixTwoSixAgg.setStrategyCap(address(eTST), cap); + + FourSixTwoSixAgg.Strategy memory strategy = fourSixTwoSixAgg.getStrategy(address(eTST)); + + assertEq(strategy.cap, cap); + } + + function testSetCapForInactiveStrategy() public { + uint256 cap = 1000000e18; + + vm.prank(manager); + vm.expectRevert(FourSixTwoSixAgg.InactiveStrategy.selector); + fourSixTwoSixAgg.setStrategyCap(address(0x2), cap); + } +} From 2877e6821b4720ec0b08de6bf5f1f62e18ff4e0c Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 10 Jun 2024 13:23:55 +0300 Subject: [PATCH 3/4] test: more tests --- src/FourSixTwoSixAgg.sol | 5 +- test/e2e/StrategyCapE2ETest.t.sol | 105 ++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 3 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 1b9bc81f..569681bb 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -107,6 +107,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn event AddStrategy(address indexed strategy, uint256 allocationPoints); event RemoveStrategy(address indexed _strategy); 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. @@ -310,8 +311,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn emit AdjustAllocationPoints(_strategy, strategyDataCache.allocationPoints, _newPoints); } - event SetStrategyCap(address indexed strategy, uint256 cap); - function setStrategyCap(address _strategy, uint256 _cap) external nonReentrant onlyRole(STRATEGY_MANAGER_ROLE) { Strategy memory strategyDataCache = strategies[_strategy]; @@ -642,7 +641,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn 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; + if ((strategyData.cap > 0) && (strategyData.allocated >= strategyData.cap)) return; uint256 totalAllocationPointsCache = totalAllocationPoints; uint256 totalAssetsAllocatableCache = totalAssetsAllocatable(); diff --git a/test/e2e/StrategyCapE2ETest.t.sol b/test/e2e/StrategyCapE2ETest.t.sol index 86d7d6d6..a364f8d0 100644 --- a/test/e2e/StrategyCapE2ETest.t.sol +++ b/test/e2e/StrategyCapE2ETest.t.sol @@ -43,4 +43,109 @@ contract StrategyCapE2ETest is FourSixTwoSixAggBase { vm.expectRevert(FourSixTwoSixAgg.InactiveStrategy.selector); fourSixTwoSixAgg.setStrategyCap(address(0x2), cap); } + + function testRebalanceAfterHittingCap() public { + uint256 cap = 3333333333333333333333; + vm.prank(manager); + fourSixTwoSixAgg.setStrategyCap(address(eTST), cap); + + uint256 amountToDeposit = 10000e18; + + // deposit into aggregator + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } + + // rebalance into strategy + vm.warp(block.timestamp + 86400); + { + FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + + uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints + / fourSixTwoSixAgg.totalAllocationPoints(); + + vm.prank(user1); + fourSixTwoSixAgg.rebalance(address(eTST)); + + assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); + assertEq( + (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, strategyBefore.allocated + expectedStrategyCash + ); + } + + // deposit and rebalance again, no rebalance should happen as strategy reached max cap + vm.warp(block.timestamp + 86400); + vm.startPrank(user1); + + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + + uint256 strategyAllocatedBefore = (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated; + + fourSixTwoSixAgg.rebalance(address(eTST)); + vm.stopPrank(); + + assertEq(strategyAllocatedBefore, (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated); + } + + function testRebalanceWhentargetAllocationGreaterThanCap() public { + uint256 amountToDeposit = 10000e18; + + // deposit into aggregator + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } + + // rebalance into strategy + vm.warp(block.timestamp + 86400); + { + FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + + uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints + / fourSixTwoSixAgg.totalAllocationPoints(); + + // set cap 10% less than target allocation + uint256 cap = expectedStrategyCash * 9e17 / 1e18; + vm.prank(manager); + fourSixTwoSixAgg.setStrategyCap(address(eTST), cap); + + vm.prank(user1); + fourSixTwoSixAgg.rebalance(address(eTST)); + + assertEq(fourSixTwoSixAgg.totalAllocated(), cap); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), cap); + assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, strategyBefore.allocated + cap); + } + } } From 6e8192a8805d4c894ed05216059f27a2a2b088f2 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 10 Jun 2024 13:26:25 +0300 Subject: [PATCH 4/4] chore: natspec --- src/FourSixTwoSixAgg.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 569681bb..d89c83b2 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -311,6 +311,10 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn emit AdjustAllocationPoints(_strategy, strategyDataCache.allocationPoints, _newPoints); } + /// @notice Set cap on strategy allocated amount. + /// @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) { Strategy memory strategyDataCache = strategies[_strategy];