Assessment reports>Avantis>High findings>Wrong reserve ratio returned by ,getReserveRatio, when constrained
Category: Coding Mistakes

Wrong reserve ratio returned by getReserveRatio when constrained

High Severity
High Impact
High Likelihood

Description

The public view function getReserveRatio determines, for a given reserve amount, what ratio to reserve between the junior and senior vaults. This is the target reserve ratio if the target reserve ratio results in a split that is reservable. Otherwise, it is the current reserve ratio:

function getReserveRatio(uint _reserveAmount)
public view returns (uint256) {
    if (_reserveAmount > 0) {
        uint currentReserveRatio = getCurrentReserveRatio();
        if (
            !_isNormalLiquidityMode(currentReserveRatio) ||
            !junior.hasLiquidity(
                (_reserveAmount * targetReserveRatio) / 100) ||
            !senior.hasLiquidity(
                (_reserveAmount * targetReserveRatio) / 100)
        ) {
            // constrained Liquidity Mode
            return currentReserveRatio;
        }
    }
    return targetReserveRatio;
}

However, there are two things wrong with this function. The first is that the call to senior.hasLiquidity should use _reserveAmount - (_reserveAmount * targetReserveRatio / 100) instead of the same parameter to junior.hasLiquidity because we want to check if the senior tranche has the liquidity for the rest of the assets.

The second is that there is no guarantee that the current reserve ratio returned by getCurrentReserveRatio() is a split that allows the reservation of that amount of assets. The reserve ratio this returns is just the proportion of assets, whether they are reserved or not:

function getCurrentReserveRatio() public view returns (uint256) {
    IERC20 asset = IERC20(junior.asset());
    if (asset.balanceOf(address(senior)) == 0 &&
        asset.balanceOf(address(junior)) == 0) {
        return targetReserveRatio;
    }
    return
        (100 * asset.balanceOf(address(junior))) /
        (asset.balanceOf(address(junior)) +
            asset.balanceOf(address(senior)));
}

So, for example, if the junior and senior vaults have the same amount of assets, but all of the junior is reserved and none of the senior is reserved, this will return 50%, which causes the reservation to fail. If it had returned 0% instead, the reservation would succeed, which it should because there are enough assets in the vaults to back the reservation.

Impact

Opening trades can unexpectedly revert in reserveBalance even if there should be enough liquidity to reserve the required balance.

A front-runner looking to cancel a trade open transaction by sandwiching it needs much less collateral to do so.

Recommendations

For the first issue, fix the coding mistake to correctly check if the reserved amount requires the constrained liquidity mode.

For the second issue, instead of using the current reserve ratio as the reserve ratio, the quantity that should be used is the current ratio of unreserved funds between the two tranches.

Remediation

This issue has been acknowledged by Avantis Labs, Inc., and fixes were implemented in the following commits:

Zellic © 2025Back to top ↑