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: preview harvest before withdraw #120

Merged
merged 4 commits into from
Oct 21, 2024
Merged
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
60 changes: 34 additions & 26 deletions src/module/EulerEarnVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -274,18 +274,9 @@ abstract contract EulerEarnVaultModule is ERC4626Upgradeable, ERC20VotesUpgradea
/// This function return an under-estimated amount when `_owner` is the current fee recipient address and performance fee > 0.
/// @return Amount of asset to be withdrawn.
function maxWithdraw(address _owner) public view virtual override nonReentrantView returns (uint256) {
uint256 ownerShares = _balanceOf(_owner);
uint256 assetsBeforeHarvest = _convertToAssets(ownerShares, Math.Rounding.Floor);
bool isOnlyCashReserveWithdraw = IERC20(_asset()).balanceOf(address(this)) >= assetsBeforeHarvest;

(uint256 totalAssetsExpected, uint256 totalSupplyExpected) =
_previewHarvestBeforeWithdraw(isOnlyCashReserveWithdraw);

uint256 maxAssets = ownerShares.mulDiv(
totalAssetsExpected + 1, totalSupplyExpected + 10 ** _decimalsOffset(), Math.Rounding.Floor
);
(,, uint256 maxAssets) = _maxWithdraw(_owner);

return _simulateStrategiesWithdraw(maxAssets);
return maxAssets;
}

/// @notice Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault.
Expand All @@ -294,18 +285,7 @@ abstract contract EulerEarnVaultModule is ERC4626Upgradeable, ERC20VotesUpgradea
/// This function return an under-estimated amount when `_owner` is the current fee recipient address and performance fee > 0.
/// @return Amount of shares.
function maxRedeem(address _owner) public view virtual override nonReentrantView returns (uint256) {
uint256 ownerShares = _balanceOf(_owner);
uint256 assetsBeforeHarvest = _convertToAssets(ownerShares, Math.Rounding.Floor);
bool isOnlyCashReserveWithdraw = IERC20(_asset()).balanceOf(address(this)) >= assetsBeforeHarvest;

(uint256 totalAssetsExpected, uint256 totalSupplyExpected) =
_previewHarvestBeforeWithdraw(isOnlyCashReserveWithdraw);

uint256 maxAssets = ownerShares.mulDiv(
totalAssetsExpected + 1, totalSupplyExpected + 10 ** _decimalsOffset(), Math.Rounding.Floor
);

maxAssets = _simulateStrategiesWithdraw(maxAssets);
(uint256 totalAssetsExpected, uint256 totalSupplyExpected, uint256 maxAssets) = _maxWithdraw(_owner);

return maxAssets.mulDiv(
totalSupplyExpected + 10 ** _decimalsOffset(), totalAssetsExpected + 1, Math.Rounding.Floor
Expand Down Expand Up @@ -880,15 +860,20 @@ abstract contract EulerEarnVaultModule is ERC4626Upgradeable, ERC20VotesUpgradea

(uint256 feeAssets, uint256 feeShares) = _applyPerformanceFee(yield, cachedPerformanceFee);

// cached `totalAssetsDeposited` & `totalSupply` are accurate in this case.
totalAssetsDepositedExpected += feeAssets;
totalSupplyExpected += feeShares;
if (feeShares != 0) {
// cached `totalAssetsDeposited` & `totalSupply` are accurate in this case.
totalAssetsDepositedExpected += feeAssets;
totalSupplyExpected += feeShares;
}
}
}

return (totalAssetsDepositedExpected, totalSupplyExpected);
}

/// @dev Simulate withdrawing an amount of assets from the underlying strategies by looping through the withdrawal queue array.
/// @param _requestedAssets Amount of assets to withdraw.
/// @return Amount of assets filled by withdrawing from the underlying strategies.
function _simulateStrategiesWithdraw(uint256 _requestedAssets) private view returns (uint256) {
EulerEarnStorage storage $ = Storage._getEulerEarnStorage();
uint256 assetsRetrieved = IERC20(_asset()).balanceOf(address(this));
Expand Down Expand Up @@ -939,6 +924,29 @@ abstract contract EulerEarnVaultModule is ERC4626Upgradeable, ERC20VotesUpgradea
function _maxMint() private view returns (uint256) {
return type(uint208).max - _totalSupply();
}

/// @dev Calculate the maximum amount of the underlying asset that can be withdrawn from the owner balance,
/// while simulating a harvest call before withdraw, returning the amounts of totalAssets and totalSupply to be expected.
/// @param _owner Owner address.
/// @return Expected totalAssets() after a harvest simulation.
/// @return Expected totalSupply() after a harvest simulation.
/// @return Max assets to withdraw.
function _maxWithdraw(address _owner) private view returns (uint256, uint256, uint256) {
uint256 ownerShares = _balanceOf(_owner);
uint256 assetsBeforeHarvest = _convertToAssets(ownerShares, Math.Rounding.Floor);
bool isOnlyCashReserveWithdraw = IERC20(_asset()).balanceOf(address(this)) >= assetsBeforeHarvest;

(uint256 totalAssetsExpected, uint256 totalSupplyExpected) =
_previewHarvestBeforeWithdraw(isOnlyCashReserveWithdraw);

uint256 maxAssets = ownerShares.mulDiv(
totalAssetsExpected + 1, totalSupplyExpected + 10 ** _decimalsOffset(), Math.Rounding.Floor
);

maxAssets = _simulateStrategiesWithdraw(maxAssets);

return (totalAssetsExpected, totalSupplyExpected, maxAssets);
}
}

contract EulerEarnVault is EulerEarnVaultModule {
Expand Down
Loading