1- import { formatUserSummary } from '@aave/math-utils' ;
1+ import { formatUserSummary , valueToBigNumber } from '@aave/math-utils' ;
22import { ArrowNarrowRightIcon } from '@heroicons/react/solid' ;
3- import { Trans } from '@lingui/macro' ;
3+ import { Plural , Trans } from '@lingui/macro' ;
44import {
55 Box ,
66 Collapse ,
@@ -20,6 +20,7 @@ import { Row } from 'src/components/primitives/Row';
2020import { Warning } from 'src/components/primitives/Warning' ;
2121import { EmodeCategory } from 'src/helpers/types' ;
2222import {
23+ ComputedReserveData ,
2324 ExtendedFormattedUser ,
2425 useAppDataContext ,
2526} from 'src/hooks/app-data-provider/useAppDataProvider' ;
@@ -46,22 +47,65 @@ export enum ErrorType {
4647}
4748
4849export type EModeCategoryDisplay = EmodeCategory & {
49- available : boolean ; // indicates if the user can enter this category
50+ available : boolean ;
51+ blockReason : EModeCategoryBlockReason ;
5052} ;
5153
52- // An E-Mode category is available if the user has no borrow positions outside of the category
53- function isEModeCategoryAvailable ( user : ExtendedFormattedUser , eMode : EmodeCategory ) : boolean {
54- const borrowableReserves = eMode . assets
55- . filter ( ( asset ) => asset . borrowable )
56- . map ( ( asset ) => asset . underlyingAsset ) ;
54+ export type EModeCategoryBlockReason = {
55+ incompatibleBorrows : string [ ] ;
56+ zeroLtvCollateral : string [ ] ;
57+ } ;
5758
58- const hasIncompatiblePositions = user . userReservesData . some (
59- ( userReserve ) =>
60- Number ( userReserve . scaledVariableDebt ) > 0 &&
61- ! borrowableReserves . includes ( userReserve . reserve . underlyingAsset )
59+ // Checks why an E-Mode category is unavailable for the user
60+ function getEModeCategoryBlockReason (
61+ user : ExtendedFormattedUser ,
62+ eMode : EmodeCategory ,
63+ reservesByAddress : Map < string , ComputedReserveData >
64+ ) : EModeCategoryBlockReason {
65+ const incompatibleBorrows : string [ ] = [ ] ;
66+ const zeroLtvCollateral : string [ ] = [ ] ;
67+
68+ // Check 1: Incompatible borrows
69+ const borrowableReserves = new Set (
70+ eMode . assets . filter ( ( asset ) => asset . borrowable ) . map ( ( asset ) => asset . underlyingAsset )
6271 ) ;
6372
64- return ! hasIncompatiblePositions ;
73+ for ( const userReserve of user . userReservesData ) {
74+ if (
75+ valueToBigNumber ( userReserve . scaledVariableDebt ) . gt ( 0 ) &&
76+ ! borrowableReserves . has ( userReserve . reserve . underlyingAsset )
77+ ) {
78+ incompatibleBorrows . push ( userReserve . reserve . symbol ) ;
79+ }
80+ }
81+
82+ // Check 2: Collateral with 0 LTV in target category
83+ for ( const userReserve of user . userReservesData ) {
84+ if ( ! userReserve . usageAsCollateralEnabledOnUser ) continue ;
85+
86+ const reserve = reservesByAddress . get ( userReserve . reserve . underlyingAsset ) ;
87+ if ( ! reserve ) continue ;
88+
89+ const reserveTargetEmode = reserve . eModes . find ( ( e ) => e . id === eMode . id ) ;
90+
91+ if (
92+ reserveTargetEmode &&
93+ reserveTargetEmode . collateralEnabled &&
94+ reserveTargetEmode . ltvzeroEnabled
95+ ) {
96+ zeroLtvCollateral . push ( reserve . symbol ) ;
97+ } else if ( ! reserveTargetEmode || ! reserveTargetEmode . collateralEnabled ) {
98+ if ( Number ( reserve . baseLTVasCollateral ) === 0 ) {
99+ zeroLtvCollateral . push ( reserve . symbol ) ;
100+ }
101+ }
102+ }
103+
104+ return { incompatibleBorrows, zeroLtvCollateral } ;
105+ }
106+
107+ function isEModeCategoryAvailable ( blockReason : EModeCategoryBlockReason ) : boolean {
108+ return blockReason . incompatibleBorrows . length === 0 && blockReason . zeroLtvCollateral . length === 0 ;
65109}
66110
67111export const EmodeModalContent = ( { user } : { user : ExtendedFormattedUser } ) => {
@@ -80,14 +124,20 @@ export const EmodeModalContent = ({ user }: { user: ExtendedFormattedUser }) =>
80124 const { gasLimit, mainTxState : emodeTxState , txError } = useModalContext ( ) ;
81125 const [ disableEmode , setDisableEmode ] = useState ( false ) ;
82126
127+ const reservesByAddress = new Map ( reserves . map ( ( r ) => [ r . underlyingAsset , r ] ) ) ;
128+
83129 const eModeCategories : Record < number , EModeCategoryDisplay > = Object . fromEntries (
84- Object . entries ( eModes ) . map ( ( [ key , value ] ) => [
85- key ,
86- {
87- ...value ,
88- available : isEModeCategoryAvailable ( user , value ) ,
89- } ,
90- ] )
130+ Object . entries ( eModes ) . map ( ( [ key , value ] ) => {
131+ const blockReason = getEModeCategoryBlockReason ( user , value , reservesByAddress ) ;
132+ return [
133+ key ,
134+ {
135+ ...value ,
136+ available : isEModeCategoryAvailable ( blockReason ) ,
137+ blockReason,
138+ } ,
139+ ] ;
140+ } )
91141 ) ;
92142
93143 // For Horizon markets, use the next available category after [1]
@@ -136,22 +186,22 @@ export const EmodeModalContent = ({ user }: { user: ExtendedFormattedUser }) =>
136186 const zeroLtvCollateralSymbols = user . userReservesData
137187 . filter (
138188 ( userReserve ) =>
139- Number ( userReserve . scaledATokenBalance ) > 0 &&
189+ valueToBigNumber ( userReserve . scaledATokenBalance ) . gt ( 0 ) &&
140190 userReserve . reserve . baseLTVasCollateral === '0' &&
141- userReserve . usageAsCollateralEnabledOnUser &&
142- userReserve . reserve . reserveLiquidationThreshold !== '0'
191+ userReserve . usageAsCollateralEnabledOnUser
143192 )
144193 . map ( ( r ) => r . reserve . symbol ) ;
145194
146195 // error handling
147196 let blockingError : ErrorType | undefined = undefined ;
148- // if user is disabling eMode
149197 if ( user . isInEmode && disableEmode ) {
150198 if ( zeroLtvCollateralSymbols . length > 0 ) {
151199 blockingError = ErrorType . ZERO_LTV_COLLATERAL_BLOCKING ;
152200 } else if ( Number ( newSummary . healthFactor ) < 1.01 && newSummary . healthFactor !== '-1' ) {
153201 blockingError = ErrorType . EMODE_DISABLED_LIQUIDATION ;
154202 }
203+ } else if ( ! disableEmode && ! selectedEmode . available ) {
204+ blockingError = ErrorType . CLOSE_POSITIONS_BEFORE_SWITCHING ;
155205 }
156206
157207 const Blocked : React . FC = ( ) => {
@@ -184,6 +234,33 @@ export const EmodeModalContent = ({ user }: { user: ExtendedFormattedUser }) =>
184234 </ Typography >
185235 </ Warning >
186236 ) ;
237+ case ErrorType . CLOSE_POSITIONS_BEFORE_SWITCHING : {
238+ const { incompatibleBorrows, zeroLtvCollateral } = selectedEmode . blockReason ;
239+ return (
240+ < Warning severity = "info" sx = { { mt : 6 , alignItems : 'center' } } >
241+ < Typography variant = "subheader1" >
242+ < Trans > Cannot switch to this category</ Trans >
243+ </ Typography >
244+ { incompatibleBorrows . length > 0 && (
245+ < Typography variant = "caption" >
246+ < Trans >
247+ Repay your { incompatibleBorrows . join ( ', ' ) } { ' ' }
248+ < Plural value = { incompatibleBorrows . length } one = "borrow" other = "borrows" /> to use
249+ this category.
250+ </ Trans >
251+ </ Typography >
252+ ) }
253+ { zeroLtvCollateral . length > 0 && (
254+ < Typography variant = "caption" >
255+ < Trans >
256+ Disable { zeroLtvCollateral . join ( ', ' ) } as collateral to use this category. These
257+ assets would have 0% LTV.
258+ </ Trans >
259+ </ Typography >
260+ ) }
261+ </ Warning >
262+ ) ;
263+ }
187264 default :
188265 return null ;
189266 }
@@ -390,14 +467,6 @@ export const EmodeModalContent = ({ user }: { user: ExtendedFormattedUser }) =>
390467 ) ) }
391468 </ Select >
392469 </ Stack >
393- { ! selectedEmode . available && (
394- < Typography variant = "caption" color = "text.secondary" sx = { { mb : 3 } } >
395- < Trans >
396- All borrow positions outside of this category must be closed to enable this
397- category.
398- </ Trans >
399- </ Typography >
400- ) }
401470 < Divider />
402471 < Row
403472 captionVariant = "description"
0 commit comments