Skip to content

[Audit V2] F-04 — convertToAssets vs real liquidity dependency #55

Description

@cozcan
  • Severity: 🟡 Medium — Score: 48 — Priority: P1
  • Category: Liquidity risk / external protocol integration
  • Location: _byzantineLiquid(), maxWithdraw(), maxRedeem(), harvest()
  • Confidence: High
  • Source: @crypto4all audit

Description

_byzantineLiquid() values the Byzantine position via convertToAssets — an accounting valuation, not a guarantee of available liquidity:

function _byzantineLiquid() internal view returns (uint256) {
    uint256 idle      = IERC20(asset()).balanceOf(address(this));
    uint256 byzShares = BYZANTINE_VAULT.balanceOf(address(this));
    uint256 byzAssets = byzShares > 0
        ? BYZANTINE_VAULT.convertToAssets(byzShares) : 0;
    return idle + byzAssets;
}

Yet _withdraw attempts a real withdrawal:

if (idle < assets) {
    BYZANTINE_VAULT.withdraw(assets - idle, address(this), address(this));
}

Under Morpho liquidity stress (over-utilized markets, withdrawal cascade), BYZANTINE_VAULT.maxWithdraw() drops well below convertToAssets(). Result: maxWithdraw on the PacktolVault side promises available funds, but the actual withdraw call reverts. The code comment even admits bypassing Byzantine's maxWithdraw() — amplifying the divergence.

Mitigating Factors

The situation is self-resolving once Morpho liquidity is restored. Funds are not lost, only temporarily inaccessible.

Recommendation 1

Introduce a dedicated function for real liquidity, distinct from the accounting valuation:

function _byzantineWithdrawable() internal view returns (uint256) {
    uint256 shares = BYZANTINE_VAULT.balanceOf(address(this));
    if (shares == 0) return 0;
    uint256 theoretical  = BYZANTINE_VAULT.convertToAssets(shares);
    uint256 withdrawable = BYZANTINE_VAULT.maxWithdraw(address(this));
    return withdrawable < theoretical ? withdrawable : theoretical;
}

Recommendation 2

Use _byzantineWithdrawable() in maxWithdraw / maxRedeem. Retain convertToAssets exclusively in totalAssets() (accounting value), never as a liquidity proxy.


Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions