# 56343 sc low morphoyearnogweth deallocate function always emits strategydeallocationloss due to flawed balance measurement

**Submitted on Oct 14th 2025 at 18:22:27 UTC by @DashBug for** [**Audit Comp | Alchemix V3**](https://immunefi.com/audit-competition/alchemix-v3-audit-competition)

* **Report ID:** #56343
* **Report Type:** Smart Contract
* **Report severity:** Low
* **Target:** <https://github.com/alchemix-finance/v3-poc/blob/immunefi\\_audit/src/strategies/mainnet/MorphoYearnOGWETH.sol>
* **Impacts:**
  * Contract fails to deliver promised returns, but doesn't lose value

## Description

## Brief/Intro

The `_deallocate` function in `src/strategies/mainnet/MorphoYearnOGWETH.sol` contains flawed balance measurement logic that causes it to always emit `StrategyDeallocationLoss` events, even when no actual loss occurs. This creates misleading performance signals and false negative reporting to users and monitoring systems.

## Vulnerability Details

* The `_deallocate` function measures balance before and after withdrawal incorrectly:

```49:61:src/strategies/mainnet/morphoyearnogweth.sol
    function _deallocate(uint256 amount) internal override returns (uint256) {
        vault.withdraw(amount, address(this), address(this));
        uint256 wethBalanceBefore = TokenUtils.safeBalanceOf(address(weth), address(this));
        uint256 wethBalanceAfter = TokenUtils.safeBalanceOf(address(weth), address(this));
        uint256 wethRedeemed = wethBalanceAfter - wethBalanceBefore;
        if (wethRedeemed < amount) {
            emit StrategyDeallocationLoss("Strategy deallocation loss.", amount, wethRedeemed);
        }
        require(wethRedeemed + wethBalanceBefore >= amount, "Strategy balance is less than the amount needed");
        require(TokenUtils.safeBalanceOf(address(weth), address(this)) >= amount, "Strategy balance is less than the amount needed");
        TokenUtils.safeApprove(address(weth), msg.sender, amount);
        return amount;
    }
```

* The balance measurement occurs **after** the withdrawal on both lines 51 and 52
* Since both measurements are taken at the same point, `wethBalanceBefore` equals `wethBalanceAfter`
* This results in `wethRedeemed = 0` always
* The condition `if (wethRedeemed < amount)` is always true (since 0 < amount)
* `StrategyDeallocationLoss` is **always emitted** regardless of actual performance

## Impact Details

* **Misleading events**: Users see loss events even when the strategy performs correctly
* **False negative reporting**: External monitoring systems receive incorrect loss signals
* **Reputation damage**: The strategy appears to always underperform
* **No actual financial loss**: The contract still delivers promised returns, just with incorrect reporting
* **Severity**: Low (logic error affecting reporting, not financial security)

## References

* `src/strategies/mainnet/MorphoYearnOGWETH.sol`:
  * Flawed balance measurement (both measurements after withdrawal)

```51:52:src/strategies/mainnet/morphoyearnogweth.sol
        uint256 wethBalanceBefore = TokenUtils.safeBalanceOf(address(weth), address(this));
        uint256 wethBalanceAfter = TokenUtils.safeBalanceOf(address(weth), address(this));
```

* Always true condition leading to false loss emission

```54:56:src/strategies/mainnet/morphoyearnogweth.sol
        if (wethRedeemed < amount) {
            emit StrategyDeallocationLoss("Strategy deallocation loss.", amount, wethRedeemed);
        }
```

## Recommendation

Fix the balance measurement by taking the "before" measurement before the withdrawal:

```solidity
function _deallocate(uint256 amount) internal override returns (uint256) {
    uint256 wethBalanceBefore = TokenUtils.safeBalanceOf(address(weth), address(this));
    vault.withdraw(amount, address(this), address(this));
    uint256 wethBalanceAfter = TokenUtils.safeBalanceOf(address(weth), address(this));
    uint256 wethRedeemed = wethBalanceAfter - wethBalanceBefore;
    if (wethRedeemed < amount) {
        emit StrategyDeallocationLoss("Strategy deallocation loss.", amount, wethRedeemed);
    }
    require(wethRedeemed + wethBalanceBefore >= amount, "Strategy balance is less than the amount needed");
    require(TokenUtils.safeBalanceOf(address(weth), address(this)) >= amount, "Strategy balance is less than the amount needed");
    TokenUtils.safeApprove(address(weth), msg.sender, amount);
    return amount;
}
```

This ensures accurate loss reporting and eliminates false negative signals.

## Link to Proof of Concept

<https://gist.github.com/dash19991226/6766247cd19c790a47bb6240e6c1b735>

## Proof of Concept

## Proof of Concept

### Runnable PoC Test

A working Proof of Concept test is available at:

```
src/test/strategies/MorphoYearnOGWETH_AlwaysEmitsLoss_PoC.t.sol
```

**To run the PoC:**

```bash
forge test --match-contract MorphoYearnOGWETH_AlwaysEmitsLoss_PoC -vv
```

**Expected output:**

```
[PASS] test_AlwaysEmitsLoss() (gas: 17587)
Logs:
  === MorphoYearnOGWETH Always Emits Loss PoC ===
  BUG DEMONSTRATION:
  1. Both balance measurements happen AFTER withdrawal
  2. wethRedeemed = wethBalanceAfter - wethBalanceBefore = 0
  3. Since 0 < amount, StrategyDeallocationLoss is ALWAYS emitted
  4. This happens even when there's no actual loss!
  Simulated values:
  Amount: 100000000000000000000
  wethBalanceBefore: 1000000000000000000000
  wethBalanceAfter: 1000000000000000000000
  wethRedeemed: 0
  Should emit loss event: true
  PoC SUCCESS: Bug confirmed - loss event always emitted

[PASS] test_RootCauseAnalysis() (gas: 7055)
Logs:
  === Root Cause Analysis ===
  Root cause: Both balance measurements occur AFTER withdrawal
  Result: wethRedeemed is always 0, so loss event always emitted
  Fix: Measure wethBalanceBefore BEFORE withdrawal
```

The PoC demonstrates that the loss event is always emitted due to the flawed balance measurement logic, confirming the bug exists in the production code.

### Manual Steps to Reproduce

1. Call `_deallocate` function with any valid amount
2. Observe that `StrategyDeallocationLoss` event is always emitted
3. Verify that both balance measurements occur after withdrawal

**Expected vs Actual:**

* Expected: Loss event only emitted when actual loss occurs
* Actual: Loss event always emitted due to flawed measurement logic

**Root Cause Analysis:**

* Both balance measurements taken after withdrawal
* `wethRedeemed` always equals 0
* Condition `wethRedeemed < amount` always true
* False loss reporting to users and monitoring systems


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://reports.immunefi.com/alchemix-v3/56343-sc-low-morphoyearnogweth-deallocate-function-always-emits-strategydeallocationloss-due-to-flaw.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
