Wrong reserve ratio returned by getReserveRatio
when constrained
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: