Contract fails to deliver promised returns, but doesn't lose value
Description
Brief/Intro
_mytSharesDeposited is not updated in all cases correctly in AlchemistV3, when MYT funds are transferred out. This leads to an overestimation of the current protocol collateralization, as well as reduced liquidation fees.
Vulnerability Details
_mytSharesDeposited is intended to differentiate between tokens deposited into a CDP and balance of the contract. However during functions _forceRepay(), _liquidate() and _doLiquidation(), MYT tokens may be transferred out of the contract but not accounted for by reducing _mytSharesDeposited accordingly. This leads to _mytSharesDeposited being artificially high.
An artificially high _mytSharesDeposited value leads to an overestimation of the protocol's current collateralization and an artificially high value for getTotalUnderlyingValue(). In partial liquidations and in cases where alchemistCurrentCollateralization is slightly higher than alchemistMinimumCollateralizatio, this overestimation can lead to liquidations incorrectly skipping setting outsourcedFee to non-zero. Thus leading to a reduce fee and incentive for partial liquidations:
functioncalculateLiquidation(uint256collateral,uint256debt,uint256targetCollateralization,uint256alchemistCurrentCollateralization,uint256alchemistMinimumCollateralization,uint256feeBps)publicpurereturns(uint256grossCollateralToSeize,uint256debtToBurn,uint256fee,uint256outsourcedFee){if(debt >= collateral){ outsourcedFee =(debt * feeBps)/ BPS;// fully liquidate debt if debt is greater than collateralreturn(collateral, debt,0, outsourcedFee);}if(alchemistCurrentCollateralization < alchemistMinimumCollateralization){ outsourcedFee =(debt * feeBps)/ BPS;// fully liquidate debt in high ltv global environmentreturn(debt, debt,0, outsourcedFee);}// fee is taken from surplus = collateral - debtuint256 surplus = collateral > debt ? collateral - debt :0; fee =(surplus * feeBps)/ BPS;// collateral remaining for margin‐restore calcuint256 adjCollat = collateral - fee;// compute m*d (both plain units)uint256 md =(targetCollateralization * debt)/ FIXED_POINT_SCALAR;// if md <= adjCollat, nothing to liquidateif(md <= adjCollat){return(0,0, fee,0);}// numerator = md - adjCollatuint256 num = md - adjCollat;// denom = m - 1 => (targetCollateralization - FIXED_POINT_SCALAR)/FIXED_POINT_SCALARuint256 denom = targetCollateralization - FIXED_POINT_SCALAR;// debtToBurn = (num * FIXED_POINT_SCALAR) / denom debtToBurn =(num * FIXED_POINT_SCALAR)/ denom;// gross collateral seize = net + fee grossCollateralToSeize = debtToBurn + fee;}
Overestimating protocol MYT shares leads to an overestimation of protocol health, which is systemically dangerous. Furthermore, during partial liquidations, this overestimation can lead to the liquidator's outsourcedFee being skipped entirely, leading to loss of incentive for liquidators to act during high global LTV situations.
In my following PoC, two users deposit the same amount of tokens, but user 2 is fully liquidated, leading to:
No outsourcedFee for partial liquidation of user 1, when there should have been
Reported protocol current collateralization appearing 2x bigger than it should, because the full liquidation transferred collateral tokens out but didn't reduce tracked balance. Thus protocol collateral massively overstated