Contract fails to deliver promised returns, but doesn't lose value
Description
This report is intended to be submitted under my team account "A2Security" but I reached the report submission rate limit on the last day. Please count this report as though it were from "A2Security".
Impact
users could use rebalanceUp to rebalance down, if their loan.stableInterest
Recomendation
To fix this we need to simply check that the userloan.stableInterestRate is not lower than the pool interest rate
function executeRebalanceUp( mapping(bytes32 => LoanManagerState.UserLoan) storage userLoans, mapping(uint8 => IHubPool) storage pools, DataTypes.ExecuteRebalanceParams memory params ) external { LoanManagerState.UserLoan storage userLoan = userLoans[params.loanId]; // user cannot rebalance borrow which they don't have or is not stable // borrow present iff loan type created and pool added so no need to check this if (!userLoan.hasStableBorrowIn(params.poolId)) { revert NoStableBorrowInLoanForPool(params.loanId, params.poolId); } // pool pre-checks and update interest indexes IHubPool pool = pools[params.poolId]; // @note update index + checks threshold DataTypes.BorrowPoolParams memory borrowPoolParams = pool.preparePoolForRebalanceUp();+ if (borrowPoolParams.stableInterestRate < userLoan.stableInterestRate) {+ revert RateLowerThanPoolRate+ } // rebalance the user loan borrow LoanManagerState.UserLoanBorrow storage loanBorrow = userLoan.borrows[params.poolId]; }
Proof of concept
Proof Of Concept
function prepareForRebalanceUp(HubPoolState.PoolData storage pool) external returns (DataTypes.BorrowPoolParams memory borrowPoolParams) {
// can rebalance even if pool is depreciated// update interest indexes before the interest rates change pool.updateInterestIndexes(); uint256 utilizationRatio = MathUtils.calcUtilisationRatio(pool.variableBorrowData.totalAmount + pool.stableBorrowData.totalAmount, pool.depositData.totalAmount);
// @note Calculates threshold as: (rebalanceUpRate(capsdata) * totalVariableRate) / 100// @note totalVariableRate = varaible.vr0 + variable.vr1 + variable.vr2uint256 rebalanceUpThreshold = MathUtils.calcRebalanceUpThreshold( pool.stableBorrowData.rebalanceUpDepositInterestRate, pool.variableBorrowData.vr0, pool.variableBorrowData.vr1, pool.variableBorrowData.vr2
);// @note U can't rebalance UP if we are under a rebalance threshold// check conditions for rebalance @1if (utilizationRatio < MathUtils.from4DPto18DP(pool.stableBorrowData.rebalanceUpUtilisationRatio)) {revertRebalanceUpUtilisationRatioNotReached(); } @2if (rebalanceUpThreshold < pool.depositData.interestRate) revertRebalanceUpThresholdNotReached(); borrowPoolParams.variableInterestIndex = pool.variableBorrowData.interestIndex; borrowPoolParams.stableInterestRate = pool.stableBorrowData.interestRate; }
As we can see in the rates checks in prepareForRebalanceUp() we don't use the userLoan.stableInterestRate and don't do any validation on it, meaning if the userLoan.stableRate is bigger than pool.stableInterestRate, and the condition @1 and @2 are fullfilled a user can still call RebalanceUp to lower the stableRate of his loan