Bad debt may persist even after complete liquidation in Velo Vault due to truncation
When liquidating a user, if all their collateral is taken but it is not valuable enough to repay the entire loan they would be left with remaining debt. This is what is known as bad debt because there is no collateral left to take and the user has no obligation to pay it back. When this occurs, the vault will forgive the user's debts, clearing the bad debt. The problem is that the valuations are calculated in two different ways which can lead to truncation issue that completely liquidates a user but doesn't clear their bad debt.\\n```\\n        uint256 totalUserCollateral = totalCollateralValue(_collateralAddress, _loanHolder);\\n        uint256 proposedLiquidationAmount;\\n        { //scope block for liquidationAmount due to stack too deep\\n            uint256 liquidationAmount = viewLiquidatableAmount(totalUserCollateral, 1 ether, isoUSDBorrowed, liquidatableMargin);\\n            require(liquidationAmount > 0 , "Loan not liquidatable");\\n            proposedLiquidationAmount = _calculateProposedReturnedCapital(_collateralAddress, _loanNFTs, _partialPercentage);\\n            require(proposedLiquidationAmount <= liquidationAmount, "excessive liquidation suggested");\\n        }\\n        uint256 isoUSDreturning = proposedLiquidationAmount*LIQUIDATION_RETURN/LOAN_SCALE;\\n        if(proposedLiquidationAmount >= totalUserCollateral){\\n            //@audit bad debt cleared here\\n        }\\n```\\n\\nThe primary check before clearing bad debt is to check if `proposedLiquidationAmount >= totalUserCollateral`. The purpose of this check is to confirm that all of the user's collateral is being liquidated. The issue is that each value is calculated differently.\\n```\\nfunction totalCollateralValue(address _collateralAddress, address _owner) public view returns(uint256){\\n    NFTids memory userNFTs = loanNFTids[_collateralAddress][_owner];\\n    IDepositReceipt depositReceipt = IDepositReceipt(_collateralAddress);\\n    //slither-disable-next-line uninitialized-local-variables\\n    uint256 totalPooledTokens;\\n    for(uint256 i =0; i < NFT_LIMIT; i++){\\n        //check if each slot contains an NFT\\n        if (userNFTs.ids[i] != 0){\\n            totalPooledTokens += depositReceipt.pooledTokens(userNFTs.ids[i]);\\n        }\\n    }\\n    return(depositReceipt.priceLiquidity(totalPooledTokens));\\n}\\n```\\n\\n`totalCollateralValue` it used to calculate `totalUserCollateral`. In this method the pooled tokens are summed across all NFT's then they are priced. This means that the value of the liquidity is truncated exactly once.\\n```\\nfunction _calculateProposedReturnedCapital(\\n    address _collateralAddress, \\n    CollateralNFTs calldata _loanNFTs, \\n    uint256 _partialPercentage\\n    ) internal view returns(uint256){\\n    //slither-disable-next-line uninitialized-local-variables\\n    uint256 proposedLiquidationAmount;\\n    require(_partialPercentage <= LOAN_SCALE, "partialPercentage greater than 100%");\\n    for(uint256 i = 0; i < NFT_LIMIT; i++){\\n            if(_loanNFTs.slots[i] < NFT_LIMIT){\\n                if((i == NFT_LIMIT -1) && (_partialPercentage > 0) && (_partialPercentage < LOAN_SCALE) ){\\n                    //final slot is NFT that will be split if necessary\\n                    proposedLiquidationAmount += \\n                                                (( _priceCollateral(IDepositReceipt(_collateralAddress), _loanNFTs.ids[i]) \\n                                                *_partialPercentage)/ LOAN_SCALE);\\n                } \\n                else {\\n                    proposedLiquidationAmount += _priceCollateral(IDepositReceipt(_collateralAddress), _loanNFTs.ids[i]);\\n                }\\n            }\\n        }\\n    return proposedLiquidationAmount;\\n}\\n```\\n\\n`_calculateProposedReturnedCapital` is used to calculate `proposedLiquidationAmount`. The key difference is that each NFT is priced individually. The result is that the value is truncated up to NFT_LIMIT times. This can lead to `proposedLiquidationAmount` being less than totalUserCollateral even if all user collateral is being liquidated.\\nExample: User A has 2 NFTs. They are valued as follows assuming no truncation: 10.6 and 10.7. When calculating via `totalCollateralValue` they will be summed before they are truncated while in `_calculateProposedReturnedCapital` they will be truncated before they are summed.\\ntotalCollateralValue: 10.6 + 10.7 = 21.3 => 21 (truncated)\\n_calculateProposedReturnedCapital: 10.6 => 10 (truncated) 10.7 => 10 (truncated)\\n10 + 10 = 20\\nAs shown above when using the exact same inputs into our two different functions the final answer is different. In a scenario like this, even though all collateral is taken from the user, their bad debt won't be cleared.
`_calculateProposedReturnedCapital` should be changed to be similar to `totalCollateralValue`, summing all pooled tokens before pricing:\\n```\\n    function _calculateProposedReturnedCapital(\\n        address _collateralAddress, \\n        CollateralNFTs calldata _loanNFTs, \\n        uint256 _partialPercentage\\n    ) internal view returns(uint256) {\\n+       IDepositReceipt depositReceipt = IDepositReceipt(_collateralAddress);\\n        //slither-disable-next-line uninitialized-local-variables\\n+       uint256 totalPooledTokens\\n-       uint256 proposedLiquidationAmount;\\n        require(_partialPercentage <= LOAN_SCALE, "partialPercentage greater than 100%");\\n        for(uint256 i = 0; i < NFT_LIMIT; i++){\\n            if(_loanNFTs.slots[i] < NFT_LIMIT){\\n                if((i == NFT_LIMIT -1) && (_partialPercentage > 0) && (_partialPercentage < LOAN_SCALE) ){\\n                    //final slot is NFT that will be split if necessary\\n+                   totalPooledTokens += ((depositReceipt.pooledTokens(userNFTs.ids[i]) * _partialPercentage) / LOAN_SCALE);\\n-                   proposedLiquidationAmount += \\n-                                            (( _priceCollateral(IDepositReceipt(_collateralAddress), _loanNFTs.ids[i]) \\n-                                           *_partialPercentage)/ LOAN_SCALE);\\n                } \\n                else{\\n+                   totalPooledTokens += depositReceipt.pooledTokens(userNFTs.ids[i]);\\n-                   proposedLiquidationAmount += _priceCollateral(IDepositReceipt(_collateralAddress), _loanNFTs.ids[i]);\\n                }\\n            }\\n        }\\n+       return(depositReceipt.priceLiquidity(totalPooledTokens));\\n-       return proposedLiquidationAmount;\\n    }\\n```\\n
Bad debt will not be cleared in some liquidation scenarios
```\\n        uint256 totalUserCollateral = totalCollateralValue(_collateralAddress, _loanHolder);\\n        uint256 proposedLiquidationAmount;\\n        { //scope block for liquidationAmount due to stack too deep\\n            uint256 liquidationAmount = viewLiquidatableAmount(totalUserCollateral, 1 ether, isoUSDBorrowed, liquidatableMargin);\\n            require(liquidationAmount > 0 , "Loan not liquidatable");\\n            proposedLiquidationAmount = _calculateProposedReturnedCapital(_collateralAddress, _loanNFTs, _partialPercentage);\\n            require(proposedLiquidationAmount <= liquidationAmount, "excessive liquidation suggested");\\n        }\\n        uint256 isoUSDreturning = proposedLiquidationAmount*LIQUIDATION_RETURN/LOAN_SCALE;\\n        if(proposedLiquidationAmount >= totalUserCollateral){\\n            //@audit bad debt cleared here\\n        }\\n```\\n