diff --git a/solidity/contracts/money-market/facets/BorrowFacet.sol b/solidity/contracts/money-market/facets/BorrowFacet.sol index c2ee2287..4d18eac2 100644 --- a/solidity/contracts/money-market/facets/BorrowFacet.sol +++ b/solidity/contracts/money-market/facets/BorrowFacet.sol @@ -48,12 +48,7 @@ contract BorrowFacet is IBorrowFacet { /// @param _subAccountId An index to derive the subaccount /// @param _token The token to borrow /// @param _amount The amount to borrow - function borrow( - address _account, - uint256 _subAccountId, - address _token, - uint256 _amount - ) external nonReentrant { + function borrow(address _account, uint256 _subAccountId, address _token, uint256 _amount) external nonReentrant { LibMoneyMarket01.MoneyMarketDiamondStorage storage moneyMarketDs = LibMoneyMarket01.moneyMarketDiamondStorage(); LibMoneyMarket01.onlyLive(moneyMarketDs); @@ -335,6 +330,14 @@ contract BorrowFacet is IBorrowFacet { ) { revert BorrowFacet_InvalidAssetTier(); } + // Edge case: when demote multiple market to ISOLATE at once + // there could be ISOLATE token along with something else in subaccount, forbid user from borrowing anything + if ( + moneyMarketDs.subAccountDebtShares[_subAccount].has(_token) && + moneyMarketDs.subAccountDebtShares[_subAccount].size > 1 + ) { + revert BorrowFacet_InvalidAssetTier(); + } // Revert if trying to borrow other tier while borrowing ISOLATE token } else if (_hasIsolateAsset) { revert BorrowFacet_InvalidAssetTier(); diff --git a/solidity/tests/money-market/over-collat/MoneyMarket_OverCollatBorrow_Borrow.t.sol b/solidity/tests/money-market/over-collat/MoneyMarket_OverCollatBorrow_Borrow.t.sol index 7fb5e1a4..a3fd3200 100644 --- a/solidity/tests/money-market/over-collat/MoneyMarket_OverCollatBorrow_Borrow.t.sol +++ b/solidity/tests/money-market/over-collat/MoneyMarket_OverCollatBorrow_Borrow.t.sol @@ -6,6 +6,7 @@ import { MoneyMarket_BaseTest, MockERC20, console } from "../MoneyMarket_BaseTes // libraries import { LibMoneyMarket01 } from "../../../contracts/money-market/libraries/LibMoneyMarket01.sol"; import { LibDoublyLinkedList } from "../../../contracts/money-market/libraries/LibDoublyLinkedList.sol"; +import { LibConstant } from "../../../contracts/money-market/libraries/LibConstant.sol"; // interfaces import { IBorrowFacet } from "../../../contracts/money-market/interfaces/IBorrowFacet.sol"; import { IAdminFacet } from "../../../contracts/money-market/interfaces/IAdminFacet.sol"; @@ -414,4 +415,61 @@ contract MoneyMarket_OverCollatBorrow_BorrowTest is MoneyMarket_BaseTest { _poolId = viewFacet.getMiniFLPoolIdOfToken(_debtToken); assertEq(_miniFL.getUserTotalAmountOf(_poolId, BOB), _borrowAmount); } + + function testRevert_WhenDemoteMarket_UserHasMoreThanOneIsolate_ShouldNotBeAbleToBorrow() public { + // prepare + usdc.mint(ALICE, 1e10); + vm.prank(ALICE); + accountManager.deposit(address(usdc), 1e10); + + btc.mint(BOB, 1 ether); + mockOracle.setTokenPrice(address(btc), 30000 ether); + + // BOB borrow weth and usdc + vm.startPrank(BOB); + btc.approve(address(accountManager), type(uint256).max); + accountManager.addCollateralFor(BOB, subAccount0, address(btc), 1 ether); + accountManager.borrow(subAccount0, address(weth), 1 ether); + accountManager.borrow(subAccount0, address(usdc), 1e8); + vm.stopPrank(); + + // Demote both token to ISOLATE tier + IAdminFacet.TokenConfigInput[] memory tokenConfigs = new IAdminFacet.TokenConfigInput[](2); + tokenConfigs[0] = IAdminFacet.TokenConfigInput({ + tier: LibConstant.AssetTier.ISOLATE, + collateralFactor: 9000, + borrowingFactor: 9000, + maxBorrow: 30 ether, + maxCollateral: 100 ether + }); + tokenConfigs[1] = IAdminFacet.TokenConfigInput({ + tier: LibConstant.AssetTier.ISOLATE, + collateralFactor: 9000, + borrowingFactor: 9000, + maxBorrow: normalizeEther(100 ether, 6), + maxCollateral: normalizeEther(100 ether, 6) + }); + address[] memory tokens = new address[](2); + tokens[0] = address(weth); + tokens[1] = address(usdc); + adminFacet.setTokenConfigs(tokens, tokenConfigs); + + // Should not be able to borrow anything after both got demoted + vm.startPrank(BOB); + vm.expectRevert(abi.encodeWithSelector(IBorrowFacet.BorrowFacet_InvalidAssetTier.selector)); + accountManager.borrow(subAccount0, address(usdc), 1e8); + vm.expectRevert(abi.encodeWithSelector(IBorrowFacet.BorrowFacet_InvalidAssetTier.selector)); + accountManager.borrow(subAccount0, address(btc), 0.01 ether); + // Should be able to repay + accountManager.repayFor(BOB, subAccount0, address(usdc), 1e8, 1e8); + + // After repay one ISOLATE debt + // should be able to borrow more of the remaining token + accountManager.borrow(subAccount0, address(weth), 0.01 ether); + // should not be able to borrow any other token + vm.expectRevert(abi.encodeWithSelector(IBorrowFacet.BorrowFacet_InvalidAssetTier.selector)); + accountManager.borrow(subAccount0, address(usdc), 1e8); + vm.expectRevert(abi.encodeWithSelector(IBorrowFacet.BorrowFacet_InvalidAssetTier.selector)); + accountManager.borrow(subAccount0, address(btc), 0.01 ether); + } }