DeFiFoundrySolidity
16,653 OP
View results
Submission Details
Severity: medium
Valid

Lifecycle Gaps in `_harvestAndReport` Function

Issue:

The _harvestAndReport function calculates the strategy’s total assets but does not claim or convert WETH into the asset (e.g., alETH). This causes profits generated as claimable WETH to remain unrealized unless a Keeper explicitly calls claimAndSwap. Consequently:

  1. Unrealized Yield: The share price does not reflect yield derived from claimable WETH, potentially underrepresenting the strategy’s profitability.

  2. Time Dependency: The realization of profits becomes entirely dependent on timely Keeper interventions.


Impact:

  1. Delayed Yield Realization:

    • Yield remains unconverted and unrealized, leading to share prices that do not accurately reflect the strategy’s performance.

  2. Operational Inefficiency:

    • If Keepers fail to call claimAndSwap at optimal times, potential profits may not be reflected in the strategy, resulting in missed opportunities for compounding.

  3. User Frustration:

    • Users may perceive the strategy as underperforming because yields are not reflected in their shares, even though the claimable WETH exists.

Evidence from Code:

function _harvestAndReport() internal override returns (uint256 _totalAssets) {
uint256 claimable = transmuter.getClaimableBalance(address(this));
// NOTE : WETH claiming and swapping logic commented out
// if (claimable > 0) {
// transmuter.claim(claimable, address(this));
// }
uint256 unexchanged = transmuter.getUnexchangedBalance(address(this));
uint256 underlyingBalance = underlying.balanceOf(address(this));
// Calculating total assets without claiming or converting WETH
_totalAssets = unexchanged + asset.balanceOf(address(this)) + underlyingBalance;
}
  • The function acknowledges claimable WETH but does not process or realize it.


Potential Attack / Problem Scenario:

  1. Yield Underreporting:

    • The strategy generates claimable WETH, but _harvestAndReport does not claim or swap it, causing the share price to underreport the actual value of the strategy.

  2. Keeper Dependency:

    • If Keepers fail to call claimAndSwap on time, users may not see realized yield in their shares. This adds operational complexity and risks underperformance.

  3. Missed Compounding:

    • Without timely claiming and swapping, WETH cannot be converted into asset and redeployed for compounding.


Proposed Solution:

Enhance _harvestAndReport to include the logic for claiming and converting WETH to asset. This ensures that all yield is realized and reflected in the share price.


Modified _harvestAndReport Function:

function _harvestAndReport() internal override returns (uint256 _totalAssets) {
uint256 claimable = transmuter.getClaimableBalance(address(this));
// Claim and convert WETH to asset (alETH) if there are claimable rewards
if (claimable > 0) {
transmuter.claim(claimable, address(this));
_swapClaimableToAsset(claimable);
}
uint256 unexchanged = transmuter.getUnexchangedBalance(address(this));
uint256 underlyingBalance = underlying.balanceOf(address(this));
// Update total assets to include realized profits
_totalAssets = unexchanged + asset.balanceOf(address(this)) + underlyingBalance;
}
function _swapClaimableToAsset(uint256 _amount) internal {
uint256 minOut = _getMinOutForSwap(_amount); // Calculate minOut based on oracle or predefined logic
IVeloRouter(router).swapExactTokensForTokens(_amount, minOut, _getSwapPath(), address(this), block.timestamp);
}
function _getMinOutForSwap(uint256 _amount) internal view returns (uint256) {
// Placeholder for price oracle integration or static pricing
return _amount * 99 / 100; // Assume 1% slippage for simplicity
}
function _getSwapPath() internal view returns (IVeloRouter.route[] memory) {
// Define or fetch the swap path dynamically
IVeloRouter.route[] memory path = new IVeloRouter.route[]();
path[0] = IVeloRouter.route(address(underlying), address(asset), false, address(router));
return path;
}

Mitigation Steps:

  1. Automate Claiming:

    • Integrate WETH claiming and swapping into _harvestAndReport to avoid relying solely on Keeper calls.

  2. Oracle-Based Pricing:

    • Use on-chain oracles to calculate minOut dynamically for swaps, ensuring efficient and secure conversions.

  3. Fallback Pathing:

    • Ensure robust swap path validation to handle different token pairs and routing configurations.


Proof of Concept (PoC):

  1. Scenario:

    • The strategy has 100 asset in “unexchanged” balance and 50 WETH in “claimable” balance.

  2. Execution:

    • _harvestAndReport is called.

    • The function claims 50 WETH, swaps it into asset, and adds it to the total assets.

  3. Expected Outcome:

    • totalAssets now reflects the combined value of unexchanged assets and realized claimable WETH.

    • The share price increases, accurately reflecting the strategy’s yield.

Conclusion:

By incorporating claimable WETH into _harvestAndReport, the strategy can fully realize profits and reflect them in the share price. This modification eliminates Keeper dependency, ensures efficient utilization of assets, and aligns user expectations with actual strategy performance.

Updates

Appeal created

inallhonesty Lead Judge 8 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Incorrect accounting in `_harvestAndReport` claimable should be included

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.