Reserve Ratio is wrongly calculated, protocol may not be able to pay liabilities in full.
Vulnerability Detail
The equation for calculating Reserve Ratio is:
Reserve Ratio = Total Reserve / Total Liability
Total Reserve is the total Asset (USDT) balance controlled by Unitas contract and InsurancePool contract, and Total Liability is total minted Stable (USD1, USDEMC) value, the same can be seen in _getTotalReservesAndCollaterals() function and _getTotalLiabilities() function:
When calculating Total Reserve, protocols obtains _balance of Unitas contract and InsurancePool contract:
mapping(address => uint256) internal _balance;
A portion of _balance goes to Curve/Aave/Balancer etc to generate yield, this part of value is stored in _portfolio:
mapping(address => uint256) internal _portfolio;
We can see that Total Reserve contains _portfolio and _portfolio is fed to the Reserve Ratio calculation. However, when user redeems, there may not be enough Asset for withdrawl.
Imagine a situation where the _balance of Unitas contract and InsurancePool contract is 1000 USDT and 300 USDT, the _portfolioof InsurancePool is 100 USDT, Total Liability is 1000 USD1, and Reserve Ratio is 130%.
If USDEMC appreciates upto 21%, Total Liability will be 1210 USD1, and Reserve Ratio is 108%, It may seem that protocol can still pay liabilities in full, but it is not.
Firstly, all the 1000 USDT in Unitas contract will be utilized, then 210 USDT needs to be obtained from InsurancePool protocol, unfortunately, the total USDT in InsurancePool contract is only 200 USDT because the 100 USDT _portfolio is not in the contract, the shortage is 10 USDT and protocol fails to pay liabilities.
Impact
Reserve Ratio is wrongly calculated, protocol may not be able to pay liabilities in full.
Jiamin
high
Reserve Ratio is wrongly calculated
Summary
Reserve Ratio is wrongly calculated, protocol may not be able to pay liabilities in full.
Vulnerability Detail
The equation for calculating Reserve Ratio is:
Total Reserve
is the total Asset (USDT) balance controlled by Unitas contract and InsurancePool contract, andTotal Liability
is total minted Stable (USD1, USDEMC) value, the same can be seen in _getTotalReservesAndCollaterals() function and _getTotalLiabilities() function:When calculating
Total Reserve
, protocols obtains _balance of Unitas contract and InsurancePool contract:A portion of
_balance
goes to Curve/Aave/Balancer etc to generate yield, this part of value is stored in _portfolio:We can see that
Total Reserve
contains_portfolio
and_portfolio
is fed to theReserve Ratio
calculation. However, when user redeems, there may not be enough Asset for withdrawl.Imagine a situation where the
_balance
of Unitas contract and InsurancePool contract is 1000 USDT and 300 USDT, the_portfolio
of InsurancePool is 100 USDT,Total Liability
is 1000 USD1, andReserve Ratio
is 130%.If USDEMC appreciates upto 21%,
Total Liability
will be 1210 USD1, andReserve Ratio
is 108%, It may seem that protocol can still pay liabilities in full, but it is not.Firstly, all the 1000 USDT in Unitas contract will be utilized, then 210 USDT needs to be obtained from InsurancePool protocol, unfortunately, the total USDT in InsurancePool contract is only 200 USDT because the 100 USDT
_portfolio
is not in the contract, the shortage is 10 USDT and protocol fails to pay liabilities.Impact
Reserve Ratio is wrongly calculated, protocol may not be able to pay liabilities in full.
Code Snippet
https://github.com/sherlock-audit/2023-04-unitasprotocol/blob/main/Unitas-Protocol/src/Unitas.sol#L500
Tool used
Manual Review
Recommendation
Please consider to exclude
_portfolio
from theReserve Ratio
calculation.Duplicate of #95