From 8851afa466a089ccd38e178c73611a4b702c97a0 Mon Sep 17 00:00:00 2001
From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com>
Date: Tue, 14 May 2024 15:43:39 +0300
Subject: [PATCH 01/11] fix

---
 src/FourSixTwoSixAgg.sol | 35 +++++++++++++++++++++++++++--------
 1 file changed, 27 insertions(+), 8 deletions(-)

diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol
index 9aa127ac..ed09ad8e 100644
--- a/src/FourSixTwoSixAgg.sol
+++ b/src/FourSixTwoSixAgg.sol
@@ -8,6 +8,9 @@ import {ERC4626, IERC4626} from "openzeppelin-contracts/token/ERC20/extensions/E
 import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol";
 import {AccessControlEnumerable} from "openzeppelin-contracts/access/AccessControlEnumerable.sol";
 
+
+import {console2} from "forge-std/Test.sol";
+
 // @note Do NOT use with fee on transfer tokens
 // @note Do NOT use with rebasing tokens
 // @note Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol
@@ -246,7 +249,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable {
     function _withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares)
         internal
         override
-    {
+    {        
         totalAssetsDeposited -= assets;
 
         uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this));
@@ -258,7 +261,8 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable {
             Strategy memory strategyData = strategies[withdrawalQueue[i]];
             IERC4626 strategy = IERC4626(withdrawalQueue[i]);
 
-            harvest(address(strategy));
+            _harvest(address(strategy));
+            _gulp();
 
             uint256 sharesBalance = strategy.balanceOf(address(this));
             uint256 underlyingBalance = strategy.convertToAssets(sharesBalance);
@@ -285,6 +289,10 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable {
     }
 
     function gulp() public nonReentrant {
+        _gulp();
+    }
+
+    function _gulp() internal {
         ESRSlot memory esrSlotCache = updateInterestAndReturnESRSlotCache();
         uint256 toGulp = totalAssetsAllocatable() - totalAssetsDeposited - esrSlotCache.interestLeft;
 
@@ -324,10 +332,12 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable {
             return; //nothing to rebalance as this is the cash reserve
         }
 
+        Strategy memory strategyData = strategies[strategy];
+
         // Harvest profits, also gulps and updates interest
-        harvest(strategy);
+        _harvest(strategy);
+        _gulp();
 
-        Strategy memory strategyData = strategies[strategy];
         uint256 totalAllocationPointsCache = totalAllocationPoints;
         uint256 totalAssetsAllocatableCache = totalAssetsAllocatable();
         uint256 targetAllocation =
@@ -381,12 +391,23 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable {
     /// @notice Harvest positive yield.
     /// @param strategy address of strategy
     function harvest(address strategy) public nonReentrant {
+        _harvest(strategy);
+
+        _gulp();
+    }
+
+    function _harvest(address strategy) internal {
         Strategy memory strategyData = strategies[strategy];
+
+        if (strategyData.allocated == 0) return;
+
         uint256 sharesBalance = IERC4626(strategy).balanceOf(address(this));
         uint256 underlyingBalance = IERC4626(strategy).convertToAssets(sharesBalance);
 
-        // There's yield!
-        if (underlyingBalance > strategyData.allocated) {
+        if (underlyingBalance == strategyData.allocated) {
+            return;
+        } else if (underlyingBalance > strategyData.allocated) {
+            // There's yield!
             uint256 yield = underlyingBalance - strategyData.allocated;
             strategies[strategy].allocated = uint120(underlyingBalance);
             totalAllocated += yield;
@@ -395,8 +416,6 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable {
             // TODO handle losses
             revert NegativeYield();
         }
-
-        gulp();
     }
 
     /// @notice Adjust a certain strategy's allocation points.

From 5b882b71c01e718c98a915a5dfc035ebcaae6c58 Mon Sep 17 00:00:00 2001
From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com>
Date: Tue, 14 May 2024 15:43:51 +0300
Subject: [PATCH 02/11] test: init e2e tests

---
 .../e2e/DepositRebalanceWithdrawE2ETest.t.sol | 93 +++++++++++++++++++
 1 file changed, 93 insertions(+)
 create mode 100644 test/e2e/DepositRebalanceWithdrawE2ETest.t.sol

diff --git a/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol
new file mode 100644
index 00000000..e5afc1d1
--- /dev/null
+++ b/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity ^0.8.0;
+
+import {FourSixTwoSixAggBase, FourSixTwoSixAgg, console2} from "../common/FourSixTwoSixAggBase.t.sol";
+
+contract DepositRebalanceWithdrawE2ETest 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 testSingleStrategy_NoYield() 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();
+
+            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, expectedStrategyCash);
+        }
+
+        vm.warp(block.timestamp + 86400);
+        // partial withdraw, no need to withdraw from strategy as cash reserve is enough
+        uint256 amountToWithdraw = 6000e18;
+        {
+            FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST));
+            uint256 strategyShareBalanceBefore = eTST.balanceOf(address(fourSixTwoSixAgg));
+            uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited();
+            uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply();
+            uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1);
+
+            vm.prank(user1);
+            fourSixTwoSixAgg.withdraw(amountToWithdraw, user1, user1);
+
+            assertEq(eTST.balanceOf(address(fourSixTwoSixAgg)), strategyShareBalanceBefore);
+            assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw);
+            assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw);
+            assertEq(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToWithdraw);
+        }
+
+        // full withdraw, will have to withdraw from strategy as cash reserve is not enough
+        {
+            amountToWithdraw = amountToDeposit - amountToWithdraw;
+            FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST));
+            uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited();
+            uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply();
+            uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1);
+
+            vm.prank(user1);
+            fourSixTwoSixAgg.withdraw(amountToWithdraw, user1, user1);
+
+            assertEq(eTST.balanceOf(address(fourSixTwoSixAgg)), 0);
+            assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw);
+            assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw);
+            assertEq(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToWithdraw);
+        }
+    }
+}

From b282b3466b972b2f22a43e8454621ef3789409b4 Mon Sep 17 00:00:00 2001
From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com>
Date: Tue, 14 May 2024 15:44:05 +0300
Subject: [PATCH 03/11] lint

---
 src/FourSixTwoSixAgg.sol | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol
index ed09ad8e..8c6fbc04 100644
--- a/src/FourSixTwoSixAgg.sol
+++ b/src/FourSixTwoSixAgg.sol
@@ -8,7 +8,6 @@ import {ERC4626, IERC4626} from "openzeppelin-contracts/token/ERC20/extensions/E
 import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol";
 import {AccessControlEnumerable} from "openzeppelin-contracts/access/AccessControlEnumerable.sol";
 
-
 import {console2} from "forge-std/Test.sol";
 
 // @note Do NOT use with fee on transfer tokens
@@ -249,7 +248,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable {
     function _withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares)
         internal
         override
-    {        
+    {
         totalAssetsDeposited -= assets;
 
         uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this));

From d18e619c8374350d8cbc59f706b035c404a33db9 Mon Sep 17 00:00:00 2001
From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com>
Date: Tue, 14 May 2024 20:45:16 +0300
Subject: [PATCH 04/11] test: more tests

---
 src/FourSixTwoSixAgg.sol                      |  2 -
 test/common/FourSixTwoSixAggBase.t.sol        |  2 +-
 .../e2e/DepositRebalanceWithdrawE2ETest.t.sol | 75 ++++++++++++++++++-
 3 files changed, 74 insertions(+), 5 deletions(-)

diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol
index 8c6fbc04..9c143d6e 100644
--- a/src/FourSixTwoSixAgg.sol
+++ b/src/FourSixTwoSixAgg.sol
@@ -8,8 +8,6 @@ import {ERC4626, IERC4626} from "openzeppelin-contracts/token/ERC20/extensions/E
 import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol";
 import {AccessControlEnumerable} from "openzeppelin-contracts/access/AccessControlEnumerable.sol";
 
-import {console2} from "forge-std/Test.sol";
-
 // @note Do NOT use with fee on transfer tokens
 // @note Do NOT use with rebasing tokens
 // @note Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol
diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol
index 8c39c86d..1ec3c7d7 100644
--- a/test/common/FourSixTwoSixAggBase.t.sol
+++ b/test/common/FourSixTwoSixAggBase.t.sol
@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 pragma solidity ^0.8.0;
 
-import {EVaultTestBase, TestERC20, console2} from "evk/test/unit/evault/EVaultTestBase.t.sol";
+import {EVaultTestBase, TestERC20, console2, EVault} from "evk/test/unit/evault/EVaultTestBase.t.sol";
 import {FourSixTwoSixAgg} from "../../src/FourSixTwoSixAgg.sol";
 
 contract FourSixTwoSixAggBase is EVaultTestBase {
diff --git a/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol
index e5afc1d1..d6eb3295 100644
--- a/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol
+++ b/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol
@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 pragma solidity ^0.8.0;
 
-import {FourSixTwoSixAggBase, FourSixTwoSixAgg, console2} from "../common/FourSixTwoSixAggBase.t.sol";
+import {FourSixTwoSixAggBase, FourSixTwoSixAgg, console2, EVault} from "../common/FourSixTwoSixAggBase.t.sol";
 
 contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase {
     uint256 user1InitialBalance = 100000e18;
@@ -51,7 +51,9 @@ contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase {
 
             assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash);
             assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash);
-            assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedStrategyCash);
+            assertEq(
+                (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, strategyBefore.allocated + expectedStrategyCash
+            );
         }
 
         vm.warp(block.timestamp + 86400);
@@ -71,6 +73,7 @@ contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase {
             assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw);
             assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw);
             assertEq(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToWithdraw);
+            assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, strategyBefore.allocated);
         }
 
         // full withdraw, will have to withdraw from strategy as cash reserve is not enough
@@ -88,6 +91,74 @@ contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase {
             assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw);
             assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw);
             assertEq(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToWithdraw);
+            assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, 0);
+        }
+    }
+
+    function testSingleStrategy_WithYield() 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();
+
+            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, expectedStrategyCash);
+        }
+
+        vm.warp(block.timestamp + 86400);
+        uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg));
+        // mock an increase of strategy balance by 10%
+        vm.mockCall(
+            address(eTST),
+            abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)),
+            abi.encode(aggrCurrentStrategyBalance * 11e17 / 1e18)
+        );
+
+        // full withdraw, will have to withdraw from strategy as cash reserve is not enough
+        {
+            uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1);
+            FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST));
+            uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited();
+            uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply();
+            uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1);
+
+            vm.prank(user1);
+            fourSixTwoSixAgg.redeem(amountToWithdraw, user1, user1);
+            vm.clearMockedCalls();
+
+            assertEq(eTST.balanceOf(address(fourSixTwoSixAgg)), 0);
+            assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw);
+            assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw);
+            assertEq(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + fourSixTwoSixAgg.convertToAssets(amountToWithdraw));
         }
     }
 }

From f22d65e703b19fec878d6a9fadc03c87a5953dba Mon Sep 17 00:00:00 2001
From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com>
Date: Tue, 14 May 2024 20:45:25 +0300
Subject: [PATCH 05/11] lint

---
 test/e2e/DepositRebalanceWithdrawE2ETest.t.sol | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol
index d6eb3295..dc7d85e9 100644
--- a/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol
+++ b/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol
@@ -158,7 +158,10 @@ contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase {
             assertEq(eTST.balanceOf(address(fourSixTwoSixAgg)), 0);
             assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw);
             assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw);
-            assertEq(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + fourSixTwoSixAgg.convertToAssets(amountToWithdraw));
+            assertEq(
+                assetTST.balanceOf(user1),
+                user1AssetTSTBalanceBefore + fourSixTwoSixAgg.convertToAssets(amountToWithdraw)
+            );
         }
     }
 }

From 039974937b27872cd30bc26d3f6dd203e9a708fe Mon Sep 17 00:00:00 2001
From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com>
Date: Tue, 14 May 2024 21:33:20 +0300
Subject: [PATCH 06/11] chore: update config

---
 .github/workflows/test.yml | 2 +-
 foundry.toml               | 5 +++++
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index cc22c240..6c33e4bc 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -30,7 +30,7 @@ jobs:
               run: forge fmt --check    
 
             - name: Run foundry tests
-              run: forge test -vv --gas-report --ast 
+              run: FOUNDRY_PROFILE=test forge test -vv --gas-report --ast 
 
             - name: Run foundry fuzzing
               run: FOUNDRY_PROFILE=ci_fuzz forge test -vv
diff --git a/foundry.toml b/foundry.toml
index b625e05f..e199dc18 100644
--- a/foundry.toml
+++ b/foundry.toml
@@ -18,6 +18,11 @@ quote_style = "double"
 number_underscore = "preserve"
 override_spacing = true
 
+[profile.test]
+no_match_test = "Fuzz"
+no_match_contract = "Fuzz"
+gas_reports = ["*"]
+
 [profile.fuzz]
 runs = 1000
 max_local_rejects = 1024

From a1f6ca64cadad12d8fc0feeb2f9d5475fde8ec8d Mon Sep 17 00:00:00 2001
From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com>
Date: Tue, 14 May 2024 21:33:32 +0300
Subject: [PATCH 07/11] test: harvest() unit tests

---
 test/unit/HarvestTest.t.sol | 114 ++++++++++++++++++++++++++++++++++++
 1 file changed, 114 insertions(+)
 create mode 100644 test/unit/HarvestTest.t.sol

diff --git a/test/unit/HarvestTest.t.sol b/test/unit/HarvestTest.t.sol
new file mode 100644
index 00000000..0f68a5ae
--- /dev/null
+++ b/test/unit/HarvestTest.t.sol
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity ^0.8.0;
+
+import {FourSixTwoSixAggBase, FourSixTwoSixAgg, console2, EVault} from "../common/FourSixTwoSixAggBase.t.sol";
+
+contract HarvestTest is FourSixTwoSixAggBase {
+    uint256 user1InitialBalance = 100000e18;
+    uint256 amountToDeposit = 10000e18;
+
+    function setUp() public virtual override {
+        super.setUp();
+
+        uint256 initialStrategyAllocationPoints = 500e18;
+        _addStrategy(manager, address(eTST), initialStrategyAllocationPoints);
+
+        assetTST.mint(user1, user1InitialBalance);
+
+        // 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, expectedStrategyCash);
+        }
+    }
+
+    function testHarvest() public {
+        // no yield increase
+        FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST));
+        uint256 totalAllocatedBefore = fourSixTwoSixAgg.totalAllocated();
+
+        assertTrue(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) == strategyBefore.allocated);
+
+        vm.prank(user1);
+        fourSixTwoSixAgg.harvest(address(eTST));
+
+        assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, strategyBefore.allocated);
+        assertEq(fourSixTwoSixAgg.totalAllocated(), totalAllocatedBefore);
+
+        // positive yield
+        vm.warp(block.timestamp + 86400);
+
+        // mock an increase of strategy balance by 10%
+        uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg));
+        vm.mockCall(
+            address(eTST),
+            abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)),
+            abi.encode(aggrCurrentStrategyBalance * 11e17 / 1e18)
+        );
+
+        assertTrue(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) > strategyBefore.allocated);
+
+        vm.prank(user1);
+        fourSixTwoSixAgg.harvest(address(eTST));
+
+        assertEq(
+            (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated,
+            eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg)))
+        );
+        assertEq(
+            fourSixTwoSixAgg.totalAllocated(),
+            totalAllocatedBefore
+                + (eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) - strategyBefore.allocated)
+        );
+    }
+
+    function testHarvestNegativeYield() public {
+        vm.warp(block.timestamp + 86400);
+
+        // mock an increase of strategy balance by 10%
+        uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg));
+        vm.mockCall(
+            address(eTST),
+            abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)),
+            abi.encode(aggrCurrentStrategyBalance * 9e17 / 1e18)
+        );
+
+        FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST));
+
+        assertTrue(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) < strategyBefore.allocated);
+
+        vm.startPrank(user1);
+        vm.expectRevert(FourSixTwoSixAgg.NegativeYield.selector);
+        fourSixTwoSixAgg.harvest(address(eTST));
+    }
+}

From 0aa32728369cfde139fd49bb5a3f134e86807fa7 Mon Sep 17 00:00:00 2001
From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com>
Date: Wed, 15 May 2024 17:15:08 +0300
Subject: [PATCH 08/11] fix: load strategyData after harvesting

---
 src/FourSixTwoSixAgg.sol | 46 ++++++++++++++++++++++++++++------------
 1 file changed, 33 insertions(+), 13 deletions(-)

diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol
index 9c143d6e..7951b357 100644
--- a/src/FourSixTwoSixAgg.sol
+++ b/src/FourSixTwoSixAgg.sol
@@ -8,6 +8,8 @@ import {ERC4626, IERC4626} from "openzeppelin-contracts/token/ERC20/extensions/E
 import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol";
 import {AccessControlEnumerable} from "openzeppelin-contracts/access/AccessControlEnumerable.sol";
 
+import {console2} from "forge-std/Test.sol";
+
 // @note Do NOT use with fee on transfer tokens
 // @note Do NOT use with rebasing tokens
 // @note Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol
@@ -250,23 +252,31 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable {
         totalAssetsDeposited -= assets;
 
         uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this));
-        for (uint256 i = 0; i < withdrawalQueue.length; i++) {
+        for (uint256 i; i < withdrawalQueue.length; i++) {
             if (assetsRetrieved >= assets) {
                 break;
             }
 
-            Strategy memory strategyData = strategies[withdrawalQueue[i]];
             IERC4626 strategy = IERC4626(withdrawalQueue[i]);
 
             _harvest(address(strategy));
             _gulp();
 
+            Strategy memory strategyData = strategies[withdrawalQueue[i]];
+
             uint256 sharesBalance = strategy.balanceOf(address(this));
             uint256 underlyingBalance = strategy.convertToAssets(sharesBalance);
 
+            console2.log("assets", assets);
+            console2.log("assetsRetrieved", assetsRetrieved);
+
             uint256 desiredAssets = assets - assetsRetrieved;
             uint256 withdrawAmount = (underlyingBalance >= desiredAssets) ? desiredAssets : underlyingBalance;
 
+            console2.log("strategyData.allocated", strategyData.allocated);
+            console2.log("withdrawAmount", withdrawAmount);
+            console2.log("totalAllocated", totalAllocated);
+
             // Update allocated assets
             strategies[withdrawalQueue[i]].allocated = strategyData.allocated - uint120(withdrawAmount);
             totalAllocated -= withdrawAmount;
@@ -318,6 +328,12 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable {
         return esrSlotCache;
     }
 
+    function rebalanceMultipleStrategies(address[] calldata _strategies) external nonReentrant {
+        for (uint256 i; i < _strategies.length; ++i) {
+            _rebalance(_strategies[i]);
+        }
+    }
+
     /// @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.
@@ -325,16 +341,20 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable {
     ///         - 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 strategy) public nonReentrant {
-        if (strategy == address(0)) {
+        _rebalance(strategy);
+    }
+
+    function _rebalance(address _strategy) internal {
+        if (_strategy == address(0)) {
             return; //nothing to rebalance as this is the cash reserve
         }
 
-        Strategy memory strategyData = strategies[strategy];
-
         // Harvest profits, also gulps and updates interest
-        _harvest(strategy);
+        _harvest(_strategy);
         _gulp();
 
+        Strategy memory strategyData = strategies[_strategy];
+
         uint256 totalAllocationPointsCache = totalAllocationPoints;
         uint256 totalAssetsAllocatableCache = totalAssetsAllocatable();
         uint256 targetAllocation =
@@ -345,13 +365,13 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable {
             // Withdraw
             uint256 toWithdraw = currentAllocation - targetAllocation;
 
-            uint256 maxWithdraw = IERC4626(strategy).maxWithdraw(address(this));
+            uint256 maxWithdraw = IERC4626(_strategy).maxWithdraw(address(this));
             if (toWithdraw > maxWithdraw) {
                 toWithdraw = maxWithdraw;
             }
 
-            IERC4626(strategy).withdraw(toWithdraw, address(this), address(this));
-            strategies[strategy].allocated = uint120(currentAllocation - toWithdraw);
+            IERC4626(_strategy).withdraw(toWithdraw, address(this), address(this));
+            strategies[_strategy].allocated = uint120(currentAllocation - toWithdraw);
             totalAllocated -= toWithdraw;
         } else if (currentAllocation < targetAllocation) {
             // Deposit
@@ -367,7 +387,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable {
                 toDeposit = cashAvailable;
             }
 
-            uint256 maxDeposit = IERC4626(strategy).maxDeposit(address(this));
+            uint256 maxDeposit = IERC4626(_strategy).maxDeposit(address(this));
             if (toDeposit > maxDeposit) {
                 toDeposit = maxDeposit;
             }
@@ -377,9 +397,9 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable {
             }
 
             // Do required approval (safely) and deposit
-            IERC20(asset()).safeApprove(strategy, toDeposit);
-            IERC4626(strategy).deposit(toDeposit, address(this));
-            strategies[strategy].allocated = uint120(currentAllocation + toDeposit);
+            IERC20(asset()).safeApprove(_strategy, toDeposit);
+            IERC4626(_strategy).deposit(toDeposit, address(this));
+            strategies[_strategy].allocated = uint120(currentAllocation + toDeposit);
             totalAllocated += toDeposit;
         }
     }

From 0a30414525dde9513794a37859d963a533da6f11 Mon Sep 17 00:00:00 2001
From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com>
Date: Thu, 16 May 2024 13:11:24 +0300
Subject: [PATCH 09/11] more tests

---
 src/FourSixTwoSixAgg.sol                      |   9 --
 test/common/FourSixTwoSixAggBase.t.sol        |   2 +-
 .../e2e/DepositRebalanceWithdrawE2ETest.t.sol | 136 ++++++++++++++++--
 3 files changed, 129 insertions(+), 18 deletions(-)

diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol
index 7951b357..1200d172 100644
--- a/src/FourSixTwoSixAgg.sol
+++ b/src/FourSixTwoSixAgg.sol
@@ -8,8 +8,6 @@ import {ERC4626, IERC4626} from "openzeppelin-contracts/token/ERC20/extensions/E
 import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol";
 import {AccessControlEnumerable} from "openzeppelin-contracts/access/AccessControlEnumerable.sol";
 
-import {console2} from "forge-std/Test.sol";
-
 // @note Do NOT use with fee on transfer tokens
 // @note Do NOT use with rebasing tokens
 // @note Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol
@@ -267,16 +265,9 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable {
             uint256 sharesBalance = strategy.balanceOf(address(this));
             uint256 underlyingBalance = strategy.convertToAssets(sharesBalance);
 
-            console2.log("assets", assets);
-            console2.log("assetsRetrieved", assetsRetrieved);
-
             uint256 desiredAssets = assets - assetsRetrieved;
             uint256 withdrawAmount = (underlyingBalance >= desiredAssets) ? desiredAssets : underlyingBalance;
 
-            console2.log("strategyData.allocated", strategyData.allocated);
-            console2.log("withdrawAmount", withdrawAmount);
-            console2.log("totalAllocated", totalAllocated);
-
             // Update allocated assets
             strategies[withdrawalQueue[i]].allocated = strategyData.allocated - uint120(withdrawAmount);
             totalAllocated -= withdrawAmount;
diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol
index 1ec3c7d7..bf3298d2 100644
--- a/test/common/FourSixTwoSixAggBase.t.sol
+++ b/test/common/FourSixTwoSixAggBase.t.sol
@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 pragma solidity ^0.8.0;
 
-import {EVaultTestBase, TestERC20, console2, EVault} from "evk/test/unit/evault/EVaultTestBase.t.sol";
+import "evk/test/unit/evault/EVaultTestBase.t.sol";
 import {FourSixTwoSixAgg} from "../../src/FourSixTwoSixAgg.sol";
 
 contract FourSixTwoSixAggBase is EVaultTestBase {
diff --git a/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol
index dc7d85e9..029f6b2c 100644
--- a/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol
+++ b/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol
@@ -1,7 +1,15 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 pragma solidity ^0.8.0;
 
-import {FourSixTwoSixAggBase, FourSixTwoSixAgg, console2, EVault} from "../common/FourSixTwoSixAggBase.t.sol";
+import {
+    FourSixTwoSixAggBase,
+    FourSixTwoSixAgg,
+    console2,
+    EVault,
+    IEVault,
+    IRMTestDefault,
+    TestERC20
+} from "../common/FourSixTwoSixAggBase.t.sol";
 
 contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase {
     uint256 user1InitialBalance = 100000e18;
@@ -135,13 +143,126 @@ contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase {
         }
 
         vm.warp(block.timestamp + 86400);
-        uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg));
         // mock an increase of strategy balance by 10%
-        vm.mockCall(
-            address(eTST),
-            abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)),
-            abi.encode(aggrCurrentStrategyBalance * 11e17 / 1e18)
-        );
+        uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg));
+        uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance);
+        uint256 aggrNewStrategyShareBalance = aggrCurrentStrategyShareBalance * 11e17 / 1e18;
+        uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18;
+        uint256 yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance;
+        assetTST.mint(address(eTST), yield);
+        eTST.skim(type(uint256).max, address(fourSixTwoSixAgg));
+
+        // full withdraw, will have to withdraw from strategy as cash reserve is not enough
+        {
+            uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1);
+            FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST));
+            uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited();
+            uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply();
+            uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1);
+
+            vm.prank(user1);
+            fourSixTwoSixAgg.redeem(amountToWithdraw, user1, user1);
+
+            assertEq(eTST.balanceOf(address(fourSixTwoSixAgg)), yield);
+            assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw);
+            assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw);
+            assertEq(
+                assetTST.balanceOf(user1),
+                user1AssetTSTBalanceBefore + fourSixTwoSixAgg.convertToAssets(amountToWithdraw)
+            );
+        }
+    }
+
+    function testMultipleStrategy_WithYield() public {
+        IEVault eTSTsecondary;
+        {
+            eTSTsecondary = IEVault(coreProductLine.createVault(address(assetTST), address(oracle), unitOfAccount));
+            eTSTsecondary.setInterestRateModel(address(new IRMTestDefault()));
+
+            uint256 initialStrategyAllocationPoints = 1000e18;
+            _addStrategy(manager, address(eTSTsecondary), initialStrategyAllocationPoints);
+        }
+
+        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
+        // 2500 total points; 1000 for reserve(40%), 500(20%) for eTST, 1000(40%) for eTSTsecondary
+        // 10k deposited; 4000 for reserve, 2000 for eTST, 4000 for eTSTsecondary
+        vm.warp(block.timestamp + 86400);
+        {
+            FourSixTwoSixAgg.Strategy memory eTSTstrategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST));
+            FourSixTwoSixAgg.Strategy memory eTSTsecondarystrategyBefore =
+                fourSixTwoSixAgg.getStrategy(address(eTSTsecondary));
+
+            assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), eTSTstrategyBefore.allocated);
+            assertEq(
+                eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(fourSixTwoSixAgg))),
+                eTSTsecondarystrategyBefore.allocated
+            );
+
+            uint256 expectedeTSTStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable()
+                * eTSTstrategyBefore.allocationPoints / fourSixTwoSixAgg.totalAllocationPoints();
+            uint256 expectedeTSTsecondaryStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable()
+                * eTSTsecondarystrategyBefore.allocationPoints / fourSixTwoSixAgg.totalAllocationPoints();
+
+            assertTrue(expectedeTSTStrategyCash != 0);
+            assertTrue(expectedeTSTsecondaryStrategyCash != 0);
+
+            address[] memory strategiesToRebalance = new address[](2);
+            strategiesToRebalance[0] = address(eTST);
+            strategiesToRebalance[1] = address(eTSTsecondary);
+            vm.prank(user1);
+            fourSixTwoSixAgg.rebalanceMultipleStrategies(strategiesToRebalance);
+
+            assertEq(fourSixTwoSixAgg.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash);
+            assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedeTSTStrategyCash);
+            assertEq(
+                eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(fourSixTwoSixAgg))),
+                expectedeTSTsecondaryStrategyCash
+            );
+            assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash);
+            assertEq(
+                (fourSixTwoSixAgg.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash
+            );
+            assertEq(
+                assetTST.balanceOf(address(fourSixTwoSixAgg)),
+                amountToDeposit - (expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash)
+            );
+        }
+
+        vm.warp(block.timestamp + 86400);
+        // mock an increase of aggregator balance due to yield
+        uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg));
+        uint256 aggrCurrenteTSTUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTShareBalance);
+        uint256 aggrCurrenteTSTsecondaryShareBalance = eTSTsecondary.balanceOf(address(fourSixTwoSixAgg));
+        uint256 aggrCurrenteTSTsecondaryUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTsecondaryShareBalance);
+        uint256 aggrNeweTSTUnderlyingBalance = aggrCurrenteTSTUnderlyingBalance * 11e17 / 1e18;
+        uint256 aggrNeweTSTsecondaryUnderlyingBalance = aggrCurrenteTSTsecondaryUnderlyingBalance * 11e17 / 1e18;
+        uint256 eTSTYield = aggrNeweTSTUnderlyingBalance - aggrCurrenteTSTUnderlyingBalance;
+        uint256 eTSTsecondaryYield = aggrNeweTSTsecondaryUnderlyingBalance - aggrCurrenteTSTsecondaryUnderlyingBalance;
+
+        assetTST.mint(address(eTST), eTSTYield);
+        assetTST.mint(address(eTSTsecondary), eTSTsecondaryYield);
+        eTST.skim(type(uint256).max, address(fourSixTwoSixAgg));
+        eTSTsecondary.skim(type(uint256).max, address(fourSixTwoSixAgg));
 
         // full withdraw, will have to withdraw from strategy as cash reserve is not enough
         {
@@ -153,7 +274,6 @@ contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase {
 
             vm.prank(user1);
             fourSixTwoSixAgg.redeem(amountToWithdraw, user1, user1);
-            vm.clearMockedCalls();
 
             assertEq(eTST.balanceOf(address(fourSixTwoSixAgg)), 0);
             assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw);

From 66722469599a68e76f14cd4405cb1eb1bb2fa07b Mon Sep 17 00:00:00 2001
From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com>
Date: Thu, 16 May 2024 13:47:48 +0300
Subject: [PATCH 10/11] more tests

---
 .../e2e/DepositRebalanceWithdrawE2ETest.t.sol | 75 ++++++++++++++++++-
 1 file changed, 71 insertions(+), 4 deletions(-)

diff --git a/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol
index 029f6b2c..1405391d 100644
--- a/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol
+++ b/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol
@@ -87,7 +87,6 @@ contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase {
         // full withdraw, will have to withdraw from strategy as cash reserve is not enough
         {
             amountToWithdraw = amountToDeposit - amountToWithdraw;
-            FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST));
             uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited();
             uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply();
             uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1);
@@ -146,7 +145,6 @@ contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase {
         // mock an increase of strategy balance by 10%
         uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg));
         uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance);
-        uint256 aggrNewStrategyShareBalance = aggrCurrentStrategyShareBalance * 11e17 / 1e18;
         uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18;
         uint256 yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance;
         assetTST.mint(address(eTST), yield);
@@ -155,7 +153,6 @@ contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase {
         // full withdraw, will have to withdraw from strategy as cash reserve is not enough
         {
             uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1);
-            FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST));
             uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited();
             uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply();
             uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1);
@@ -267,7 +264,6 @@ contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase {
         // full withdraw, will have to withdraw from strategy as cash reserve is not enough
         {
             uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1);
-            FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST));
             uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited();
             uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply();
             uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1);
@@ -284,4 +280,75 @@ contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase {
             );
         }
     }
+
+    function testSingleStrategy_WithYield_WithInterest() 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();
+
+            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, expectedStrategyCash);
+        }
+
+        vm.warp(block.timestamp + 86400);
+        // mock an increase of strategy balance by 10%
+        uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg));
+        uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance);
+        uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18;
+        uint256 yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance;
+        assetTST.mint(address(eTST), yield);
+        eTST.skim(type(uint256).max, address(fourSixTwoSixAgg));
+
+        // harvest
+        vm.prank(user1);
+        fourSixTwoSixAgg.harvest(address(eTST));
+        vm.warp(block.timestamp + 2 weeks);
+
+        // full withdraw, will have to withdraw from strategy as cash reserve is not enough
+        {
+            uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1);
+            uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited();
+            uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply();
+            uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1);
+
+            vm.prank(user1);
+            fourSixTwoSixAgg.redeem(amountToWithdraw, user1, user1);
+
+            // all yield is distributed
+            assertApproxEqAbs(eTST.balanceOf(address(fourSixTwoSixAgg)), 0, 1);
+            assertApproxEqAbs(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw, 1);
+            assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw);
+            assertApproxEqAbs(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToDeposit + yield, 1);
+        }
+    }
 }

From 214303eb774188a842b61a498056ad493b34203b Mon Sep 17 00:00:00 2001
From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com>
Date: Thu, 16 May 2024 14:08:28 +0300
Subject: [PATCH 11/11] more tests

---
 src/FourSixTwoSixAgg.sol                      |   9 +-
 .../e2e/DepositRebalanceWithdrawE2ETest.t.sol | 125 ++++++++++++++++++
 2 files changed, 133 insertions(+), 1 deletion(-)

diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol
index 1200d172..4074a31a 100644
--- a/src/FourSixTwoSixAgg.sol
+++ b/src/FourSixTwoSixAgg.sol
@@ -395,7 +395,6 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable {
         }
     }
 
-    /// ToDo: possibly allow batch harvest
     /// @notice Harvest positive yield.
     /// @param strategy address of strategy
     function harvest(address strategy) public nonReentrant {
@@ -404,6 +403,14 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable {
         _gulp();
     }
 
+    function harvestMultipleStrategies(address[] calldata _strategies) external nonReentrant {
+        for (uint256 i; i < _strategies.length; ++i) {
+            _harvest(_strategies[i]);
+
+            _gulp();
+        }
+    }
+
     function _harvest(address strategy) internal {
         Strategy memory strategyData = strategies[strategy];
 
diff --git a/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol
index 1405391d..437b73b6 100644
--- a/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol
+++ b/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol
@@ -351,4 +351,129 @@ contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase {
             assertApproxEqAbs(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToDeposit + yield, 1);
         }
     }
+
+    function testMultipleStrategy_WithYield_WithInterest() public {
+        IEVault eTSTsecondary;
+        {
+            eTSTsecondary = IEVault(coreProductLine.createVault(address(assetTST), address(oracle), unitOfAccount));
+            eTSTsecondary.setInterestRateModel(address(new IRMTestDefault()));
+
+            uint256 initialStrategyAllocationPoints = 1000e18;
+            _addStrategy(manager, address(eTSTsecondary), initialStrategyAllocationPoints);
+        }
+
+        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
+        // 2500 total points; 1000 for reserve(40%), 500(20%) for eTST, 1000(40%) for eTSTsecondary
+        // 10k deposited; 4000 for reserve, 2000 for eTST, 4000 for eTSTsecondary
+        vm.warp(block.timestamp + 86400);
+        {
+            FourSixTwoSixAgg.Strategy memory eTSTstrategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST));
+            FourSixTwoSixAgg.Strategy memory eTSTsecondarystrategyBefore =
+                fourSixTwoSixAgg.getStrategy(address(eTSTsecondary));
+
+            assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), eTSTstrategyBefore.allocated);
+            assertEq(
+                eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(fourSixTwoSixAgg))),
+                eTSTsecondarystrategyBefore.allocated
+            );
+
+            uint256 expectedeTSTStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable()
+                * eTSTstrategyBefore.allocationPoints / fourSixTwoSixAgg.totalAllocationPoints();
+            uint256 expectedeTSTsecondaryStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable()
+                * eTSTsecondarystrategyBefore.allocationPoints / fourSixTwoSixAgg.totalAllocationPoints();
+
+            assertTrue(expectedeTSTStrategyCash != 0);
+            assertTrue(expectedeTSTsecondaryStrategyCash != 0);
+
+            address[] memory strategiesToRebalance = new address[](2);
+            strategiesToRebalance[0] = address(eTST);
+            strategiesToRebalance[1] = address(eTSTsecondary);
+            vm.prank(user1);
+            fourSixTwoSixAgg.rebalanceMultipleStrategies(strategiesToRebalance);
+
+            assertEq(fourSixTwoSixAgg.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash);
+            assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedeTSTStrategyCash);
+            assertEq(
+                eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(fourSixTwoSixAgg))),
+                expectedeTSTsecondaryStrategyCash
+            );
+            assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash);
+            assertEq(
+                (fourSixTwoSixAgg.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash
+            );
+            assertEq(
+                assetTST.balanceOf(address(fourSixTwoSixAgg)),
+                amountToDeposit - (expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash)
+            );
+        }
+
+        vm.warp(block.timestamp + 86400);
+        uint256 eTSTYield;
+        uint256 eTSTsecondaryYield;
+        {
+            // mock an increase of aggregator balance due to yield
+            uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg));
+            uint256 aggrCurrenteTSTUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTShareBalance);
+            uint256 aggrCurrenteTSTsecondaryShareBalance = eTSTsecondary.balanceOf(address(fourSixTwoSixAgg));
+            uint256 aggrCurrenteTSTsecondaryUnderlyingBalance =
+                eTST.convertToAssets(aggrCurrenteTSTsecondaryShareBalance);
+            uint256 aggrNeweTSTUnderlyingBalance = aggrCurrenteTSTUnderlyingBalance * 11e17 / 1e18;
+            uint256 aggrNeweTSTsecondaryUnderlyingBalance = aggrCurrenteTSTsecondaryUnderlyingBalance * 11e17 / 1e18;
+            eTSTYield = aggrNeweTSTUnderlyingBalance - aggrCurrenteTSTUnderlyingBalance;
+            eTSTsecondaryYield = aggrNeweTSTsecondaryUnderlyingBalance - aggrCurrenteTSTsecondaryUnderlyingBalance;
+
+            assetTST.mint(address(eTST), eTSTYield);
+            assetTST.mint(address(eTSTsecondary), eTSTsecondaryYield);
+            eTST.skim(type(uint256).max, address(fourSixTwoSixAgg));
+            eTSTsecondary.skim(type(uint256).max, address(fourSixTwoSixAgg));
+        }
+
+        // harvest
+        address[] memory strategiesToHarvest = new address[](2);
+        strategiesToHarvest[0] = address(eTST);
+        strategiesToHarvest[1] = address(eTSTsecondary);
+        vm.prank(user1);
+        fourSixTwoSixAgg.harvestMultipleStrategies(strategiesToHarvest);
+        vm.warp(block.timestamp + 2 weeks);
+
+        // full withdraw, will have to withdraw from strategy as cash reserve is not enough
+        {
+            uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1);
+            uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited();
+            uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply();
+            uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1);
+
+            vm.prank(user1);
+            fourSixTwoSixAgg.redeem(amountToWithdraw, user1, user1);
+
+            assertApproxEqAbs(eTST.balanceOf(address(fourSixTwoSixAgg)), 0, 0);
+            assertApproxEqAbs(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw, 1);
+            assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw);
+            assertApproxEqAbs(
+                assetTST.balanceOf(user1),
+                user1AssetTSTBalanceBefore + amountToDeposit + eTSTYield + eTSTsecondaryYield,
+                1
+            );
+        }
+    }
 }