From bea1fe56c94fbe381a02844b764452cf31cd0c77 Mon Sep 17 00:00:00 2001 From: Mark Grothe Date: Wed, 1 Jul 2026 16:00:08 -0500 Subject: [PATCH 1/4] fix: isolated emode --- .../CollateralChangeModalContent.tsx | 10 +- .../transactions/Emode/EmodeModalContent.tsx | 93 ++++++++++++++++--- .../FunCheckout/FunkitCheckout.tsx | 10 +- .../Supply/CollateralOptionsSelector.tsx | 42 ++++++++- .../Supply/SupplyModalContent.tsx | 17 ++-- .../Swap/details/CollateralSwapDetails.tsx | 3 +- .../Swap/hooks/useFlowSelector.ts | 17 +++- .../postInputs/ZeroLTVDestinationWarning.tsx | 12 ++- src/helpers/types.ts | 1 + src/hooks/pool/useUserSummaryAndIncentives.ts | 9 +- src/hooks/useZeroLTVBlockingWithdraw.tsx | 14 ++- src/locales/el/messages.js | 2 +- src/locales/en/messages.js | 2 +- src/locales/en/messages.po | 13 +++ src/locales/es/messages.js | 2 +- src/locales/fr/messages.js | 2 +- src/modules/umbrella/UnstakeModalContent.tsx | 4 +- src/store/poolSelectors.ts | 2 + src/utils/hfUtils.ts | 72 ++++++++++++-- 19 files changed, 271 insertions(+), 56 deletions(-) diff --git a/src/components/transactions/CollateralChange/CollateralChangeModalContent.tsx b/src/components/transactions/CollateralChange/CollateralChangeModalContent.tsx index f39bdc35b7..73c56f385f 100644 --- a/src/components/transactions/CollateralChange/CollateralChangeModalContent.tsx +++ b/src/components/transactions/CollateralChange/CollateralChangeModalContent.tsx @@ -10,6 +10,7 @@ import { import { useAssetCaps } from 'src/hooks/useAssetCaps'; import { useModalContext } from 'src/hooks/useModal'; import { useZeroLTVBlockingWithdraw } from 'src/hooks/useZeroLTVBlockingWithdraw'; +import { hasNonZeroEffectiveLtv } from 'src/utils/hfUtils'; import { GasEstimationError } from '../FlowCommons/GasEstimationError'; import { ModalWrapperProps } from '../FlowCommons/ModalWrapper'; @@ -49,9 +50,12 @@ export const CollateralChangeModalContent = ({ // Check if asset has non-zero LTV (base or in user's active e-mode) const userEMode = poolReserve.eModes?.find((e) => e.id === user?.userEmodeCategoryId); - const hasNonZeroLtv = - poolReserve.baseLTVasCollateral !== '0' || - (user?.isInEmode && userEMode?.collateralEnabled && !userEMode?.ltvzeroEnabled); + const hasNonZeroLtv = hasNonZeroEffectiveLtv({ + baseLTVasCollateral: poolReserve.baseLTVasCollateral, + isInEmode: !!user?.isInEmode, + emodeEntry: userEMode, + isEModeIsolated: !!eModes[user.userEmodeCategoryId]?.isolated, + }); // Find e-mode categories where this asset can be used as collateral with non-zero LTV const collateralEmodeCategories = diff --git a/src/components/transactions/Emode/EmodeModalContent.tsx b/src/components/transactions/Emode/EmodeModalContent.tsx index eeca1c6660..9133e59a8f 100644 --- a/src/components/transactions/Emode/EmodeModalContent.tsx +++ b/src/components/transactions/Emode/EmodeModalContent.tsx @@ -18,6 +18,7 @@ import { FormattedNumber } from 'src/components/primitives/FormattedNumber'; import { Link } from 'src/components/primitives/Link'; import { Row } from 'src/components/primitives/Row'; import { Warning } from 'src/components/primitives/Warning'; +import { TextWithTooltip } from 'src/components/TextWithTooltip'; import { EmodeCategory } from 'src/helpers/types'; import { ComputedReserveData, @@ -28,6 +29,7 @@ import { useCurrentTimestamp } from 'src/hooks/useCurrentTimestamp'; import { useModalContext } from 'src/hooks/useModal'; import { useWeb3Context } from 'src/libs/hooks/useWeb3Context'; import { useRootStore } from 'src/store/root'; +import { getEmodeAdjustedReserves } from 'src/utils/hfUtils'; import { getNetworkConfig } from 'src/utils/marketsAndNetworksConfig'; import { replaceUnderscoresWithSpaces } from 'src/utils/utils'; import { useShallow } from 'zustand/shallow'; @@ -96,7 +98,10 @@ function getEModeCategoryBlockReason( ) { zeroLtvCollateral.push(reserve.symbol); } else if (!reserveTargetEmode || !reserveTargetEmode.collateralEnabled) { - if (Number(reserve.baseLTVasCollateral) === 0) { + // v3.7 isolated eMode rule: assets outside the category's collateralBitmap are + // forced to 0 LTV regardless of their base LTV, so entry reverts on-chain + // (InvalidCollateralInEmode) unless this collateral is disabled first. + if (Number(reserve.baseLTVasCollateral) === 0 || eMode.isolated) { zeroLtvCollateral.push(reserve.symbol); } } @@ -172,11 +177,12 @@ export const EmodeModalContent = ({ user }: { user: ExtendedFormattedUser }) => const networkConfig = getNetworkConfig(currentChainId); // calcs + const targetEmodeId = disableEmode ? 0 : selectedEmode.id; const newSummary = formatUserSummary({ currentTimestamp, userReserves: userReserves, - formattedReserves: reserves, - userEmodeCategoryId: disableEmode ? 0 : selectedEmode.id, + formattedReserves: getEmodeAdjustedReserves(reserves, targetEmodeId, eModes), + userEmodeCategoryId: targetEmodeId, marketReferenceCurrencyDecimals, marketReferencePriceInUsd, }); @@ -393,9 +399,29 @@ export const EmodeModalContent = ({ user }: { user: ExtendedFormattedUser }) => - - Asset category - + + + Asset category + + {selectedEmode.isolated && ( + <> + + - + + Isolated} + variant="caption" + textColor="warning.main" + iconColor="warning.main" + > + + Only the listed collateral contributes borrowing power in this category — + other collateral you hold will have 0% LTV while it's active. + + + + )} +