Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Fix harvest() and more tests #8

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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
70 changes: 52 additions & 18 deletions src/FourSixTwoSixAgg.sol
Original file line number Diff line number Diff line change
Expand Up @@ -250,15 +250,17 @@ 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));
_harvest(address(strategy));
_gulp();

Strategy memory strategyData = strategies[withdrawalQueue[i]];

uint256 sharesBalance = strategy.balanceOf(address(this));
uint256 underlyingBalance = strategy.convertToAssets(sharesBalance);
Expand All @@ -285,6 +287,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;

Expand Down Expand Up @@ -313,21 +319,33 @@ 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.
/// If current allocation is less than target allocation, the aggregator will:
/// - Try to deposit the delta, if the cash is not sufficient, deposit all the available cash
/// - If all the available cash is greater than the max deposit, deposit the max deposit
function rebalance(address 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
}

// Harvest profits, also gulps and updates interest
harvest(strategy);
_harvest(_strategy);
_gulp();

Strategy memory strategyData = strategies[_strategy];

Strategy memory strategyData = strategies[strategy];
uint256 totalAllocationPointsCache = totalAllocationPoints;
uint256 totalAssetsAllocatableCache = totalAssetsAllocatable();
uint256 targetAllocation =
Expand All @@ -338,13 +356,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
Expand All @@ -360,7 +378,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;
}
Expand All @@ -370,23 +388,41 @@ 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;
}
}

/// ToDo: possibly allow batch harvest
/// @notice Harvest positive yield.
/// @param strategy address of strategy
function harvest(address strategy) public nonReentrant {
_harvest(strategy);

_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];

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;
Expand All @@ -395,8 +431,6 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable {
// TODO handle losses
revert NegativeYield();
}

gulp();
}

/// @notice Adjust a certain strategy's allocation points.
Expand Down
2 changes: 1 addition & 1 deletion test/common/FourSixTwoSixAggBase.t.sol
Original file line number Diff line number Diff line change
@@ -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 "evk/test/unit/evault/EVaultTestBase.t.sol";
import {FourSixTwoSixAgg} from "../../src/FourSixTwoSixAgg.sol";

contract FourSixTwoSixAggBase is EVaultTestBase {
Expand Down
Loading
Loading