From 10a6db8d23ecb0a6d10f86413b19efeb0060f10c Mon Sep 17 00:00:00 2001
From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com>
Date: Thu, 30 May 2024 13:42:59 +0100
Subject: [PATCH 1/8] feat: socialize negative yield

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

diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol
index 5f79f742..607dbbc1 100644
--- a/src/FourSixTwoSixAgg.sol
+++ b/src/FourSixTwoSixAgg.sol
@@ -620,8 +620,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn
 
             _accruePerformanceFee(yield);
         } else {
-            // TODO handle losses
-            revert NegativeYield();
+            uint256 socializedLoss = strategyData.allocated - underlyingBalance;
+            totalAssetsDeposited -= socializedLoss;
         }
     }
 

From 61a19e3acc110760d6b9a2617b8ba470c7f67164 Mon Sep 17 00:00:00 2001
From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com>
Date: Fri, 31 May 2024 16:52:35 +0100
Subject: [PATCH 2/8] fix and tests'

---
 src/FourSixTwoSixAgg.sol    |  7 +--
 test/unit/HarvestTest.t.sol | 85 ++++++++++++++++++++++++++++++++++++-
 2 files changed, 87 insertions(+), 5 deletions(-)

diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol
index 607dbbc1..38d758b4 100644
--- a/src/FourSixTwoSixAgg.sol
+++ b/src/FourSixTwoSixAgg.sol
@@ -10,6 +10,8 @@ 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 {Test, console2, stdError} from "forge-std/Test.sol";
+
 /// @dev Do NOT use with fee on transfer tokens
 /// @dev Do NOT use with rebasing tokens
 /// @dev Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol
@@ -437,7 +439,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn
     function updateInterestAndReturnESRSlotCache() public returns (ESRSlot memory) {
         ESRSlot memory esrSlotCache = esrSlot;
         uint256 accruedInterest = interestAccruedFromCache(esrSlotCache);
-
         // it's safe to down-cast because the accrued interest is a fraction of interest left
         esrSlotCache.interestLeft -= uint168(accruedInterest);
         esrSlotCache.lastInterestUpdate = uint40(block.timestamp);
@@ -524,7 +525,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn
 
         if (totalAssetsDeposited == 0) return;
         uint256 toGulp = totalAssetsAllocatable() - totalAssetsDeposited - esrSlotCache.interestLeft;
-
         uint256 maxGulp = type(uint168).max - esrSlotCache.interestLeft;
         if (toGulp > maxGulp) toGulp = maxGulp; // cap interest, allowing the vault to function
 
@@ -606,7 +606,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn
         Strategy memory strategyData = strategies[strategy];
 
         if (strategyData.allocated == 0) return;
-
         uint256 sharesBalance = IERC4626(strategy).balanceOf(address(this));
         uint256 underlyingBalance = IERC4626(strategy).convertToAssets(sharesBalance);
 
@@ -621,6 +620,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn
             _accruePerformanceFee(yield);
         } else {
             uint256 socializedLoss = strategyData.allocated - underlyingBalance;
+            strategies[strategy].allocated = uint120(underlyingBalance);
+            // totalAllocated -= socializedLoss;
             totalAssetsDeposited -= socializedLoss;
         }
     }
diff --git a/test/unit/HarvestTest.t.sol b/test/unit/HarvestTest.t.sol
index 0f68a5ae..e5039756 100644
--- a/test/unit/HarvestTest.t.sol
+++ b/test/unit/HarvestTest.t.sol
@@ -95,7 +95,7 @@ contract HarvestTest is FourSixTwoSixAggBase {
     function testHarvestNegativeYield() public {
         vm.warp(block.timestamp + 86400);
 
-        // mock an increase of strategy balance by 10%
+        // mock a decrease of strategy balance by 10%
         uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg));
         vm.mockCall(
             address(eTST),
@@ -107,8 +107,89 @@ contract HarvestTest is FourSixTwoSixAggBase {
 
         assertTrue(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) < strategyBefore.allocated);
 
+        vm.prank(user1);
+        fourSixTwoSixAgg.harvest(address(eTST));
+    }
+
+    function testHarvestNegativeYieldAndWithdrawSingleUser() public {
+        vm.warp(block.timestamp + 86400);
+
+        // mock a decrease of strategy balance by 10%
+        uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg));
+        uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance * 9e17 / 1e18;
+        vm.mockCall(
+            address(eTST),
+            abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)),
+            abi.encode(aggrCurrentStrategyBalanceAfterNegYield)
+        );
+
+        FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST));
+        assertTrue(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) < strategyBefore.allocated);
+        uint256 negativeYield =
+            strategyBefore.allocated - eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg)));
+
+        uint256 user1SharesBefore = fourSixTwoSixAgg.balanceOf(user1);
+        uint256 user1SocializedLoss = user1SharesBefore * negativeYield / fourSixTwoSixAgg.totalSupply();
+        uint256 expectedUser1Assets =
+            user1SharesBefore * amountToDeposit / fourSixTwoSixAgg.totalSupply() - user1SocializedLoss;
+        uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1);
+
         vm.startPrank(user1);
-        vm.expectRevert(FourSixTwoSixAgg.NegativeYield.selector);
         fourSixTwoSixAgg.harvest(address(eTST));
+        fourSixTwoSixAgg.redeem(user1SharesBefore, user1, user1);
+        vm.stopPrank();
+
+        uint256 user1SharesAfter = fourSixTwoSixAgg.balanceOf(user1);
+
+        assertEq(user1SharesAfter, 0);
+        assertApproxEqAbs(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + expectedUser1Assets, 1);
+    }
+
+    function testHarvestNegativeYieldwMultipleUser() public {
+        uint256 user2InitialBalance = 5000e18;
+        assetTST.mint(user2, user2InitialBalance);
+        // deposit into aggregator
+        {
+            vm.startPrank(user2);
+            assetTST.approve(address(fourSixTwoSixAgg), user2InitialBalance);
+            fourSixTwoSixAgg.deposit(user2InitialBalance, user2);
+            vm.stopPrank();
+        }
+
+        vm.warp(block.timestamp + 86400);
+
+        // mock a decrease of strategy balance by 10%
+        uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg));
+        uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance * 9e17 / 1e18;
+        vm.mockCall(
+            address(eTST),
+            abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)),
+            abi.encode(aggrCurrentStrategyBalanceAfterNegYield)
+        );
+
+        FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST));
+        assertTrue(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) < strategyBefore.allocated);
+        uint256 negativeYield =
+            strategyBefore.allocated - eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg)));
+        uint256 user1SharesBefore = fourSixTwoSixAgg.balanceOf(user1);
+        uint256 user1SocializedLoss = user1SharesBefore * negativeYield / fourSixTwoSixAgg.totalSupply();
+        uint256 user2SharesBefore = fourSixTwoSixAgg.balanceOf(user2);
+        uint256 user2SocializedLoss = user2SharesBefore * negativeYield / fourSixTwoSixAgg.totalSupply();
+
+        uint256 expectedUser1Assets = user1SharesBefore * (amountToDeposit + user2InitialBalance)
+            / fourSixTwoSixAgg.totalSupply() - user1SocializedLoss;
+        uint256 expectedUser2Assets = user2SharesBefore * (amountToDeposit + user2InitialBalance)
+            / fourSixTwoSixAgg.totalSupply() - user2SocializedLoss;
+
+        vm.prank(user1);
+        fourSixTwoSixAgg.harvest(address(eTST));
+
+        uint256 user1SharesAfter = fourSixTwoSixAgg.balanceOf(user1);
+        uint256 user1AssetsAfter = fourSixTwoSixAgg.convertToAssets(user1SharesAfter);
+        uint256 user2SharesAfter = fourSixTwoSixAgg.balanceOf(user2);
+        uint256 user2AssetsAfter = fourSixTwoSixAgg.convertToAssets(user2SharesAfter);
+
+        assertApproxEqAbs(user1AssetsAfter, expectedUser1Assets, 1);
+        assertApproxEqAbs(user2AssetsAfter, expectedUser2Assets, 1);
     }
 }

From 5c4746b1492bfefc1ba9f41fdfa4cd37d2ed84a6 Mon Sep 17 00:00:00 2001
From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com>
Date: Fri, 31 May 2024 16:53:10 +0100
Subject: [PATCH 3/8] decrease totalAllocated

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

diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol
index 38d758b4..cc63807c 100644
--- a/src/FourSixTwoSixAgg.sol
+++ b/src/FourSixTwoSixAgg.sol
@@ -621,7 +621,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn
         } else {
             uint256 socializedLoss = strategyData.allocated - underlyingBalance;
             strategies[strategy].allocated = uint120(underlyingBalance);
-            // totalAllocated -= socializedLoss;
+            totalAllocated -= socializedLoss;
             totalAssetsDeposited -= socializedLoss;
         }
     }

From 034c2e514dbb388f5a6f738b54cf98a367315099 Mon Sep 17 00:00:00 2001
From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com>
Date: Mon, 3 Jun 2024 14:45:09 +0100
Subject: [PATCH 4/8] fix

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

diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol
index cc63807c..57804dc0 100644
--- a/src/FourSixTwoSixAgg.sol
+++ b/src/FourSixTwoSixAgg.sol
@@ -525,6 +525,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn
 
         if (totalAssetsDeposited == 0) return;
         uint256 toGulp = totalAssetsAllocatable() - totalAssetsDeposited - esrSlotCache.interestLeft;
+        /// TODO return if toGulp == 0
         uint256 maxGulp = type(uint168).max - esrSlotCache.interestLeft;
         if (toGulp > maxGulp) toGulp = maxGulp; // cap interest, allowing the vault to function
 
@@ -619,10 +620,19 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn
 
             _accruePerformanceFee(yield);
         } else {
-            uint256 socializedLoss = strategyData.allocated - underlyingBalance;
+            uint256 loss = strategyData.allocated - underlyingBalance;
+
             strategies[strategy].allocated = uint120(underlyingBalance);
-            totalAllocated -= socializedLoss;
-            totalAssetsDeposited -= socializedLoss;
+            totalAllocated -= loss;
+
+            ESRSlot memory esrSlotCache = esrSlot;
+            if (esrSlotCache.interestLeft >= loss) {
+                esrSlotCache.interestLeft -= uint168(loss);
+            } else {
+                totalAssetsDeposited -= loss - esrSlotCache.interestLeft;
+                esrSlotCache.interestLeft = 0;
+            }
+            esrSlot = esrSlotCache;
         }
     }
 

From 0262537185ba9cc113a75ff945c81d5c84d1cb92 Mon Sep 17 00:00:00 2001
From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com>
Date: Mon, 3 Jun 2024 14:45:19 +0100
Subject: [PATCH 5/8] more gulping tests

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

diff --git a/test/unit/GulpTest.t.sol b/test/unit/GulpTest.t.sol
new file mode 100644
index 00000000..c96cb3d1
--- /dev/null
+++ b/test/unit/GulpTest.t.sol
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity ^0.8.0;
+
+import {FourSixTwoSixAggBase, FourSixTwoSixAgg, console2, EVault} from "../common/FourSixTwoSixAggBase.t.sol";
+
+contract GulpTest 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 testGulp() public {
+    //     console2.log("#testGulp");
+    //     fourSixTwoSixAgg.gulp();
+    //     FourSixTwoSixAgg.ESRSlot memory ers = fourSixTwoSixAgg.getESRSlot();
+    //     assertEq(fourSixTwoSixAgg.interestAccrued(), 0);
+    //     assertEq(ers.interestLeft, 0);
+
+    //     vm.warp(block.timestamp + 2 days);
+    //     fourSixTwoSixAgg.gulp();
+    //     assertEq(fourSixTwoSixAgg.interestAccrued(), 0);
+
+    //     vm.warp(block.timestamp + 1 days);
+    //     assertEq(fourSixTwoSixAgg.interestAccrued(), 0);
+    //     uint256 yield;
+    //     {
+    //         uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg));
+    //         uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance);
+    //         uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18;
+    //         yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance;
+    //         assetTST.mint(address(eTST), yield);
+    //         eTST.skim(type(uint256).max, address(fourSixTwoSixAgg));
+    //     }
+    //     vm.prank(user1);
+    //     fourSixTwoSixAgg.harvest(address(eTST));
+
+    //     assertEq(fourSixTwoSixAgg.interestAccrued(), 0);
+
+    //     vm.warp(block.timestamp + 1 days);
+    //     // interest per day 23.809523809523
+    //     assertEq(fourSixTwoSixAgg.interestAccrued(), 23809523809523809523);
+    //     fourSixTwoSixAgg.gulp();
+    //     ers = fourSixTwoSixAgg.getESRSlot();
+    //     assertEq(ers.interestLeft, yield - 23809523809523809523);
+
+    //     // move close to end of smearing
+    //     vm.warp(block.timestamp + 11 days);
+    //     fourSixTwoSixAgg.gulp();
+    //     ers = fourSixTwoSixAgg.getESRSlot();
+    //     // assertEq(ers.interestLeft, yield - 23809523809523809523*11);
+
+    //     // mock a decrease of strategy balance by ers.interestLeft
+    //     uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg));
+    //     uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance - ers.interestLeft;
+    //     vm.mockCall(
+    //         address(eTST),
+    //         abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)),
+    //         abi.encode(aggrCurrentStrategyBalanceAfterNegYield)
+    //     );
+    //     vm.prank(user1);
+    //     fourSixTwoSixAgg.harvest(address(eTST));
+    // }
+
+    function testGulp() public {
+        fourSixTwoSixAgg.gulp();
+        FourSixTwoSixAgg.ESRSlot memory ers = fourSixTwoSixAgg.getESRSlot();
+        assertEq(fourSixTwoSixAgg.interestAccrued(), 0);
+        assertEq(ers.interestLeft, 0);
+
+        vm.warp(block.timestamp + 2 days);
+        fourSixTwoSixAgg.gulp();
+        assertEq(fourSixTwoSixAgg.interestAccrued(), 0);
+
+        vm.warp(block.timestamp + 1 days);
+        assertEq(fourSixTwoSixAgg.interestAccrued(), 0);
+        uint256 yield;
+        {
+            uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg));
+            uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance);
+            uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18;
+            yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance;
+            assetTST.mint(address(eTST), yield);
+            eTST.skim(type(uint256).max, address(fourSixTwoSixAgg));
+        }
+        vm.prank(user1);
+        fourSixTwoSixAgg.harvest(address(eTST));
+
+        assertEq(fourSixTwoSixAgg.interestAccrued(), 0);
+
+        vm.warp(block.timestamp + 1 days);
+        // interest per day 23.809523809523
+        assertEq(fourSixTwoSixAgg.interestAccrued(), 23809523809523809523);
+        fourSixTwoSixAgg.gulp();
+        ers = fourSixTwoSixAgg.getESRSlot();
+        assertEq(ers.interestLeft, yield - 23809523809523809523);
+
+        // move close to end of smearing
+        vm.warp(block.timestamp + 11 days);
+        fourSixTwoSixAgg.gulp();
+        ers = fourSixTwoSixAgg.getESRSlot();
+        // assertEq(ers.interestLeft, yield - 23809523809523809523*11);
+
+        // mock a decrease of strategy balance by ers.interestLeft
+        uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg));
+        uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance - (ers.interestLeft * 2);
+        vm.mockCall(
+            address(eTST),
+            abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)),
+            abi.encode(aggrCurrentStrategyBalanceAfterNegYield)
+        );
+        vm.prank(user1);
+        fourSixTwoSixAgg.harvest(address(eTST));
+    }
+}

From 0ce8f6592f085db2f11cf4fd77266707d79c2aad Mon Sep 17 00:00:00 2001
From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com>
Date: Mon, 3 Jun 2024 16:44:23 +0100
Subject: [PATCH 6/8] clean

---
 src/FourSixTwoSixAgg.sol | 34 +++++++++++++++++++++++-----------
 1 file changed, 23 insertions(+), 11 deletions(-)

diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol
index 57804dc0..d15dd4fb 100644
--- a/src/FourSixTwoSixAgg.sol
+++ b/src/FourSixTwoSixAgg.sol
@@ -224,6 +224,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn
     /// @param _strategy Address of strategy to rebalance.
     function rebalance(address _strategy) external nonReentrant {
         _rebalance(_strategy);
+
+        _gulp();
     }
 
     /// @notice Rebalance multiple strategies.
@@ -232,9 +234,11 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn
         for (uint256 i; i < _strategies.length; ++i) {
             _rebalance(_strategies[i]);
         }
+
+        _gulp();
     }
 
-    /// @notice Harvest positive yield.
+    /// @notice Harvest strategy.
     /// @param strategy address of strategy
     function harvest(address strategy) external nonReentrant {
         _harvest(strategy);
@@ -242,6 +246,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn
         _gulp();
     }
 
+    /// @notice Harvest multiple strategie.
+    /// @param _strategies an array of strategy addresses.
     function harvestMultipleStrategies(address[] calldata _strategies) external nonReentrant {
         for (uint256 i; i < _strategies.length; ++i) {
             _harvest(_strategies[i]);
@@ -340,6 +346,10 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn
         withdrawalQueue.pop();
     }
 
+    function updateInterestAccrued() external returns (ESRSlot memory) {
+        return _updateInterestAccrued();
+    }
+
     function gulp() external nonReentrant {
         _gulp();
     }
@@ -366,7 +376,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn
     /// @notice Return the accrued interest
     /// @return uint256 accrued interest
     function interestAccrued() external view returns (uint256) {
-        return interestAccruedFromCache(esrSlot);
+        return _interestAccruedFromCache(esrSlot);
     }
 
     /// @notice Transfers a certain amount of tokens to a recipient.
@@ -418,7 +428,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn
         returns (uint256 shares)
     {
         // Move interest to totalAssetsDeposited
-        updateInterestAndReturnESRSlotCache();
+        _updateInterestAccrued();
         return super.withdraw(assets, receiver, owner);
     }
 
@@ -432,13 +442,13 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn
         returns (uint256 assets)
     {
         // Move interest to totalAssetsDeposited
-        updateInterestAndReturnESRSlotCache();
+        _updateInterestAccrued();
         return super.redeem(shares, receiver, owner);
     }
 
-    function updateInterestAndReturnESRSlotCache() public returns (ESRSlot memory) {
+    function _updateInterestAccrued() internal returns (ESRSlot memory) {
         ESRSlot memory esrSlotCache = esrSlot;
-        uint256 accruedInterest = interestAccruedFromCache(esrSlotCache);
+        uint256 accruedInterest = _interestAccruedFromCache(esrSlotCache);
         // it's safe to down-cast because the accrued interest is a fraction of interest left
         esrSlotCache.interestLeft -= uint168(accruedInterest);
         esrSlotCache.lastInterestUpdate = uint40(block.timestamp);
@@ -453,7 +463,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn
     /// @notice Return the total amount of assets deposited, plus the accrued interest.
     /// @return uint256 total amount
     function totalAssets() public view override returns (uint256) {
-        return totalAssetsDeposited + interestAccruedFromCache(esrSlot);
+        return totalAssetsDeposited + _interestAccruedFromCache(esrSlot);
     }
 
     /// @notice get the total assets allocatable
@@ -520,12 +530,15 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn
         super._withdraw(caller, receiver, owner, assets, shares);
     }
 
+    /// @dev gulp positive yield and increment the left interest
     function _gulp() internal {
-        ESRSlot memory esrSlotCache = updateInterestAndReturnESRSlotCache();
+        ESRSlot memory esrSlotCache = _updateInterestAccrued();
 
         if (totalAssetsDeposited == 0) return;
         uint256 toGulp = totalAssetsAllocatable() - totalAssetsDeposited - esrSlotCache.interestLeft;
-        /// TODO return if toGulp == 0
+
+        if (toGulp == 0) return;
+
         uint256 maxGulp = type(uint168).max - esrSlotCache.interestLeft;
         if (toGulp > maxGulp) toGulp = maxGulp; // cap interest, allowing the vault to function
 
@@ -550,7 +563,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn
 
         // Harvest profits, also gulps and updates interest
         _harvest(_strategy);
-        _gulp();
 
         Strategy memory strategyData = strategies[_strategy];
 
@@ -663,7 +675,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn
     /// @dev Get accrued interest without updating it.
     /// @param esrSlotCache Cached esrSlot
     /// @return uint256 accrued interest
-    function interestAccruedFromCache(ESRSlot memory esrSlotCache) internal view returns (uint256) {
+    function _interestAccruedFromCache(ESRSlot memory esrSlotCache) internal view returns (uint256) {
         // If distribution ended, full amount is accrued
         if (block.timestamp > esrSlotCache.interestSmearEnd) {
             return esrSlotCache.interestLeft;

From d55d61b1dd26b5085a98233b37310b46e0410a47 Mon Sep 17 00:00:00 2001
From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com>
Date: Mon, 3 Jun 2024 17:08:44 +0100
Subject: [PATCH 7/8] more tests

---
 test/unit/GulpTest.t.sol | 105 +++++++++++++++++++--------------------
 1 file changed, 51 insertions(+), 54 deletions(-)

diff --git a/test/unit/GulpTest.t.sol b/test/unit/GulpTest.t.sol
index c96cb3d1..3f6da94d 100644
--- a/test/unit/GulpTest.t.sol
+++ b/test/unit/GulpTest.t.sol
@@ -52,59 +52,57 @@ contract GulpTest is FourSixTwoSixAggBase {
         }
     }
 
-    // function testGulp() public {
-    //     console2.log("#testGulp");
-    //     fourSixTwoSixAgg.gulp();
-    //     FourSixTwoSixAgg.ESRSlot memory ers = fourSixTwoSixAgg.getESRSlot();
-    //     assertEq(fourSixTwoSixAgg.interestAccrued(), 0);
-    //     assertEq(ers.interestLeft, 0);
-
-    //     vm.warp(block.timestamp + 2 days);
-    //     fourSixTwoSixAgg.gulp();
-    //     assertEq(fourSixTwoSixAgg.interestAccrued(), 0);
-
-    //     vm.warp(block.timestamp + 1 days);
-    //     assertEq(fourSixTwoSixAgg.interestAccrued(), 0);
-    //     uint256 yield;
-    //     {
-    //         uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg));
-    //         uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance);
-    //         uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18;
-    //         yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance;
-    //         assetTST.mint(address(eTST), yield);
-    //         eTST.skim(type(uint256).max, address(fourSixTwoSixAgg));
-    //     }
-    //     vm.prank(user1);
-    //     fourSixTwoSixAgg.harvest(address(eTST));
-
-    //     assertEq(fourSixTwoSixAgg.interestAccrued(), 0);
-
-    //     vm.warp(block.timestamp + 1 days);
-    //     // interest per day 23.809523809523
-    //     assertEq(fourSixTwoSixAgg.interestAccrued(), 23809523809523809523);
-    //     fourSixTwoSixAgg.gulp();
-    //     ers = fourSixTwoSixAgg.getESRSlot();
-    //     assertEq(ers.interestLeft, yield - 23809523809523809523);
-
-    //     // move close to end of smearing
-    //     vm.warp(block.timestamp + 11 days);
-    //     fourSixTwoSixAgg.gulp();
-    //     ers = fourSixTwoSixAgg.getESRSlot();
-    //     // assertEq(ers.interestLeft, yield - 23809523809523809523*11);
-
-    //     // mock a decrease of strategy balance by ers.interestLeft
-    //     uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg));
-    //     uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance - ers.interestLeft;
-    //     vm.mockCall(
-    //         address(eTST),
-    //         abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)),
-    //         abi.encode(aggrCurrentStrategyBalanceAfterNegYield)
-    //     );
-    //     vm.prank(user1);
-    //     fourSixTwoSixAgg.harvest(address(eTST));
-    // }
-
-    function testGulp() public {
+    function testGulpAfterNegativeYieldEqualToInterestLeft() public {
+        fourSixTwoSixAgg.gulp();
+        FourSixTwoSixAgg.ESRSlot memory ers = fourSixTwoSixAgg.getESRSlot();
+        assertEq(fourSixTwoSixAgg.interestAccrued(), 0);
+        assertEq(ers.interestLeft, 0);
+
+        vm.warp(block.timestamp + 2 days);
+        fourSixTwoSixAgg.gulp();
+        assertEq(fourSixTwoSixAgg.interestAccrued(), 0);
+
+        vm.warp(block.timestamp + 1 days);
+        assertEq(fourSixTwoSixAgg.interestAccrued(), 0);
+        uint256 yield;
+        {
+            uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg));
+            uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance);
+            uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18;
+            yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance;
+            assetTST.mint(address(eTST), yield);
+            eTST.skim(type(uint256).max, address(fourSixTwoSixAgg));
+        }
+        vm.prank(user1);
+        fourSixTwoSixAgg.harvest(address(eTST));
+
+        assertEq(fourSixTwoSixAgg.interestAccrued(), 0);
+
+        vm.warp(block.timestamp + 1 days);
+        // interest per day 23.809523809523
+        assertEq(fourSixTwoSixAgg.interestAccrued(), 23809523809523809523);
+        fourSixTwoSixAgg.gulp();
+        ers = fourSixTwoSixAgg.getESRSlot();
+        assertEq(ers.interestLeft, yield - 23809523809523809523);
+
+        // move close to end of smearing
+        vm.warp(block.timestamp + 11 days);
+        fourSixTwoSixAgg.gulp();
+        ers = fourSixTwoSixAgg.getESRSlot();
+
+        // mock a decrease of strategy balance by ers.interestLeft
+        uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg));
+        uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance - ers.interestLeft;
+        vm.mockCall(
+            address(eTST),
+            abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)),
+            abi.encode(aggrCurrentStrategyBalanceAfterNegYield)
+        );
+        vm.prank(user1);
+        fourSixTwoSixAgg.harvest(address(eTST));
+    }
+
+    function testGulpAfterNegativeYieldBiggerThanInterestLeft() public {
         fourSixTwoSixAgg.gulp();
         FourSixTwoSixAgg.ESRSlot memory ers = fourSixTwoSixAgg.getESRSlot();
         assertEq(fourSixTwoSixAgg.interestAccrued(), 0);
@@ -141,7 +139,6 @@ contract GulpTest is FourSixTwoSixAggBase {
         vm.warp(block.timestamp + 11 days);
         fourSixTwoSixAgg.gulp();
         ers = fourSixTwoSixAgg.getESRSlot();
-        // assertEq(ers.interestLeft, yield - 23809523809523809523*11);
 
         // mock a decrease of strategy balance by ers.interestLeft
         uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg));

From 970ead04526ae40ec8ad0f62c1b945f0fe0e06e8 Mon Sep 17 00:00:00 2001
From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com>
Date: Mon, 3 Jun 2024 17:18:16 +0100
Subject: [PATCH 8/8] clean

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

diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol
index d15dd4fb..4ef2915d 100644
--- a/src/FourSixTwoSixAgg.sol
+++ b/src/FourSixTwoSixAgg.sol
@@ -10,8 +10,6 @@ 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 {Test, console2, stdError} from "forge-std/Test.sol";
-
 /// @dev Do NOT use with fee on transfer tokens
 /// @dev Do NOT use with rebasing tokens
 /// @dev Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol
@@ -246,7 +244,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn
         _gulp();
     }
 
-    /// @notice Harvest multiple strategie.
+    /// @notice Harvest multiple strategies.
     /// @param _strategies an array of strategy addresses.
     function harvestMultipleStrategies(address[] calldata _strategies) external nonReentrant {
         for (uint256 i; i < _strategies.length; ++i) {