From 80c9bd17b8419ee522448cfe175ae1ebbf09734b Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Fri, 12 May 2023 04:57:52 -0700 Subject: [PATCH 1/5] feat: remove vault stuff --- src/contexts/UserContext.tsx | 229 +---------------------------------- 1 file changed, 6 insertions(+), 223 deletions(-) diff --git a/src/contexts/UserContext.tsx b/src/contexts/UserContext.tsx index 210b803d2..b7a93ac4c 100644 --- a/src/contexts/UserContext.tsx +++ b/src/contexts/UserContext.tsx @@ -1,23 +1,12 @@ import { useRouter } from 'next/router'; -import { useContext, useEffect, useReducer, useCallback, useState, Dispatch, createContext, ReactNode } from 'react'; +import { useContext, useEffect, useReducer, useCallback, Dispatch, createContext, ReactNode } from 'react'; import { BigNumber, ethers } from 'ethers'; -import * as contractTypes from '../contracts'; - -import { - calculateAPR, - divDecimal, - bytesToBytes32, - floorDecimal, - mulDecimal, - sellFYToken, - calcAccruedDebt, - toBn, -} from '@yield-protocol/ui-math'; + +import { calculateAPR, divDecimal, floorDecimal, mulDecimal, sellFYToken, toBn } from '@yield-protocol/ui-math'; import Decimal from 'decimal.js'; -import { IAssetRoot, ISeriesRoot, IVaultRoot, ISeries, IAsset, IVault, IStrategyRoot, IStrategy } from '../types'; +import { IAssetRoot, ISeriesRoot, ISeries, IAsset, IVault, IStrategyRoot, IStrategy } from '../types'; import { ChainContext } from './ChainContext'; -import { cleanValue, generateVaultName } from '../utils/appUtils'; import { EULER_SUPGRAPH_ENDPOINT, RATE, ZERO_BN } from '../utils/constants'; import { SettingsContext } from './SettingsContext'; @@ -34,17 +23,14 @@ import useFork from '../hooks/useFork'; import { formatUnits } from 'ethers/lib/utils'; import useBalances, { BalanceData } from '../hooks/useBalances'; import useAccountPlus from '../hooks/useAccountPlus'; -import { ContractNames } from '../config/contracts'; const initState: IUserContextState = { userLoading: false, /* Item maps */ assetMap: new Map(), seriesMap: new Map(), - vaultMap: new Map(), strategyMap: new Map(), - vaultsLoading: true, seriesLoading: true, assetsLoading: true, strategiesLoading: true, @@ -61,7 +47,6 @@ const initState: IUserContextState = { const initActions: IUserContextActions = { updateSeries: () => null, updateAssets: () => null, - updateVaults: () => null, updateStrategies: () => null, setSelectedVault: () => null, setSelectedIlk: () => null, @@ -86,8 +71,6 @@ function userReducer(state: IUserContextState, action: UserContextAction): IUser case UserState.USER_LOADING: return { ...state, userLoading: action.payload }; - case UserState.VAULTS_LOADING: - return { ...state, vaultsLoading: action.payload }; case UserState.SERIES_LOADING: return { ...state, seriesLoading: action.payload }; case UserState.ASSETS_LOADING: @@ -99,14 +82,9 @@ function userReducer(state: IUserContextState, action: UserContextAction): IUser return { ...state, assetMap: new Map([...state.assetMap, ...action.payload]) }; case UserState.SERIES: return { ...state, seriesMap: new Map([...state.seriesMap, ...action.payload]) }; - case UserState.VAULTS: - return { ...state, vaultMap: new Map([...state.vaultMap, ...action.payload]) }; case UserState.STRATEGIES: return { ...state, strategyMap: new Map([...state.strategyMap, ...action.payload]) }; - case UserState.CLEAR_VAULTS: - return { ...state, vaultMap: new Map() }; - case UserState.SELECTED_VAULT: return { ...state, selectedVault: action.payload }; case UserState.SELECTED_SERIES: @@ -131,7 +109,7 @@ const UserProvider = ({ children }: { children: ReactNode }) => { const { chainLoaded, seriesRootMap, assetRootMap, strategyRootMap } = chainState; const { - settingsState: { diagnostics, useForkedEnv }, + settingsState: { diagnostics }, } = useContext(SettingsContext); /* LOCAL STATE */ @@ -139,18 +117,9 @@ const UserProvider = ({ children }: { children: ReactNode }) => { /* HOOKS */ const chainId = useChainId(); - const provider = useProvider(); - const { address: account } = useAccountPlus(); - - const { pathname } = useRouter(); - const { getTimeTillMaturity, isMature } = useTimeTillMaturity(); - const contracts = useContracts(); - - const { forkStartBlock } = useFork(); - const { // data: assetBalances, // isLoading: assetsLoading, @@ -194,74 +163,6 @@ const UserProvider = ({ children }: { children: ReactNode }) => { [diagnostics] ); - /* internal function for getting the users vaults */ - const _getVaults = useCallback(async () => { - if (!contracts) return; - - const Cauldron = contracts.get(ContractNames.CAULDRON) as contractTypes.Cauldron; - - const cacheKey = `vaults_${account}_${chainId}`; - const cachedVaults = JSON.parse(localStorage.getItem(cacheKey)!); - const cachedVaultList = (cachedVaults ?? []) as IVaultRoot[]; - - const lastVaultUpdateKey = `lastVaultUpdate_${account}_${chainId}`; - // get the latest available vault ( either from the local storage or from the forkStart) - const lastVaultUpdate = useForkedEnv - ? forkStartBlock || 'earliest' - : JSON.parse(localStorage.getItem(lastVaultUpdateKey)!) || 'earliest'; - - /* Get a list of the vaults that were BUILT */ - const vaultsBuiltFilter = Cauldron.filters.VaultBuilt(null, account, null); - const vaultsBuilt = (await Cauldron.queryFilter(vaultsBuiltFilter!, lastVaultUpdate)) || []; - const buildEventList = vaultsBuilt.map((x) => { - const { vaultId: id, ilkId, seriesId } = x.args; - const series = seriesRootMap.get(seriesId); - return { - id, - seriesId, - baseId: series?.baseId!, - ilkId, - displayName: generateVaultName(id), - decimals: series?.decimals!, - }; - }); - - /* Get a list of the vaults that were RECEIVED */ - const vaultsReceivedFilter = Cauldron.filters.VaultGiven(null, account); - const vaultsReceived = (await Cauldron.queryFilter(vaultsReceivedFilter, lastVaultUpdate)) || []; - const receivedEventsList = await Promise.all( - vaultsReceived.map(async (x): Promise => { - const { vaultId: id } = x.args; - const { ilkId, seriesId } = await Cauldron.vaults(id); - const series = seriesRootMap.get(seriesId); - return { - id, - seriesId, - baseId: series?.baseId!, - ilkId, - displayName: generateVaultName(id), - decimals: series?.decimals!, - }; - }) - ); - - console.log('vaults received', vaultsReceived); - - /* all vaults */ - const allVaultList = [ - ...buildEventList, - ...receivedEventsList, - ...cachedVaultList, // this is causing us to have vault dupes - is this intentional? - ]; - - /* Cache results */ - const latestBlock = (await provider.getBlockNumber()).toString(); - allVaultList.length && localStorage.setItem(cacheKey, JSON.stringify(allVaultList)); - allVaultList.length && localStorage.setItem(lastVaultUpdateKey, latestBlock); - - return allVaultList; - }, [account, chainId, contracts, forkStartBlock, provider, seriesRootMap, useForkedEnv]); - /* Updates the assets with relevant *user* data */ const updateAssets = useCallback( async (assetList: IAssetRoot[]) => { @@ -624,115 +525,6 @@ const UserProvider = ({ children }: { children: ReactNode }) => { [account, userState.seriesMap] // userState.strategyMap excluded on purpose ); - /* Updates the vaults with *user* data */ - const updateVaults = useCallback( - async (vaultList: IVaultRoot[] = []) => { - if (!contracts) return; - - console.log('Updating vaults ...', account); - updateState({ type: UserState.VAULTS_LOADING, payload: true }); - - let _vaults = vaultList; - const Cauldron = contracts.get(ContractNames.CAULDRON) as contractTypes.Cauldron; - const VRCauldron = contracts.get(ContractNames.VR_CAULDRON) as contractTypes.VRCauldron; - const WitchV1 = contracts.get(ContractNames.WITCH) as contractTypes.Witch; - const Witch = contracts.get(ContractNames.WITCHV2) as contractTypes.WitchV2; - const VRWitch = contracts.get(ContractNames.VR_WITCH) as contractTypes.VRWitch; - - /** - * if vaultList is empty, clear local app memory and fetch complete Vaultlist from chain via _getVaults */ - if (vaultList.length === 0) { - updateState({ type: UserState.CLEAR_VAULTS }); - _vaults = (await _getVaults()) as any; - } - - /* if fetching vaults fails */ - if (!_vaults) return; - - console.log('Updating fr _vaults with dynamic data ...', _vaults); - - const updatedVaults = await Promise.all( - _vaults.map(async (vault) => { - const [ - { ink, art }, - { owner, seriesId, ilkId }, // update balance and series (series - because a vault can have been rolled to another series) */ - ] = await Promise.all([Cauldron?.balances(vault.id), Cauldron?.vaults(vault.id)]); - - const series = seriesRootMap.get(seriesId); - if (!series) return; - - const isVaultMature = isMature(series?.maturity!); - - const liquidationEvents = !useForkedEnv - ? await Promise.all([ - WitchV1.queryFilter(Witch.filters.Bought(bytesToBytes32(vault.id, 12), null, null, null)), - Witch.queryFilter(Witch.filters.Bought(bytesToBytes32(vault.id, 12), null, null, null)), - ]) - : []; - const hasBeenLiquidated = liquidationEvents.flat().length > 0; - - let accruedArt: BigNumber; - let rateAtMaturity: BigNumber; - let rate: BigNumber; - - if (isVaultMature) { - const rateOracleAddr = await Cauldron.lendingOracles(vault.baseId); - const RateOracle = contractTypes.CompoundMultiOracle__factory.connect(rateOracleAddr, provider); // using compount multi here, but all rate oracles follow the same func sig methodology - - rateAtMaturity = await Cauldron.ratesAtMaturity(seriesId); - [rate] = await RateOracle.peek(bytesToBytes32(vault.baseId, 6), RATE, '0'); - - [accruedArt] = rateAtMaturity.gt(ZERO_BN) - ? calcAccruedDebt(rate, rateAtMaturity, art) - : calcAccruedDebt(rate, rate, art); - } else { - rate = BigNumber.from('1'); - rateAtMaturity = BigNumber.from('1'); - accruedArt = art; - } - - const baseRoot = assetRootMap.get(vault.baseId); - const ilkRoot = assetRootMap.get(ilkId); - - const newVault: IVault = { - ...vault, - owner, // refreshed in case owner has been updated - isWitchOwner: Witch.address === owner || WitchV1.address === owner, // check if witch is the owner (in liquidation process) - hasBeenLiquidated, - isActive: owner.toLowerCase() === account?.toLowerCase(), // refreshed in case owner has been updated - seriesId, // refreshed in case seriesId has been updated - ilkId, // refreshed in case ilkId has been updated - ink, - art, - accruedArt, - isVaultMature, - rateAtMaturity, - rate, - - rate_: cleanValue(ethers.utils.formatUnits(rate, 18), 2), // always 18 decimals when getting rate from rate oracle, - ink_: cleanValue(ethers.utils.formatUnits(ink, ilkRoot?.decimals), ilkRoot?.digitFormat), // for display purposes only - art_: cleanValue(ethers.utils.formatUnits(art, baseRoot?.decimals), baseRoot?.digitFormat), // for display purposes only - accruedArt_: cleanValue(ethers.utils.formatUnits(accruedArt, baseRoot?.decimals), baseRoot?.digitFormat), // display purposes - }; - - return newVault; - }) - ); - - const newVaultMap = updatedVaults.reduce((acc, item) => { - if (item) { - return acc.set(item.id, item as any); - } - return acc; - }, new Map() as Map); - updateState({ type: UserState.VAULTS, payload: newVaultMap }); - - diagnostics && console.log('Vaults updated successfully.'); - updateState({ type: UserState.VAULTS_LOADING, payload: false }); - }, - [_getVaults, account, assetRootMap, contracts, diagnostics, isMature, provider, seriesRootMap, useForkedEnv] - ); - /** * * When the chainContext is finished loading get the dynamic series and asset. @@ -743,9 +535,8 @@ const UserProvider = ({ children }: { children: ReactNode }) => { if (chainLoaded === chainId && assetRootMap.size && seriesRootMap.size) { updateAssets(Array.from(assetRootMap.values())); updateSeries(Array.from(seriesRootMap.values())); - account && updateVaults(); } - }, [account, assetRootMap, seriesRootMap, chainLoaded, chainId, updateAssets, updateSeries, updateVaults]); + }, [account, assetRootMap, seriesRootMap, chainLoaded, chainId, updateAssets, updateSeries]); /* update strategy map when series map is fetched */ useEffect(() => { @@ -755,12 +546,6 @@ const UserProvider = ({ children }: { children: ReactNode }) => { } }, [strategyRootMap, userState.seriesMap, chainLoaded, chainId, updateStrategies]); - // /* If the url references a series/vault...set that one as active */ - // useEffect(() => { - // const vaultId = pathname.split('/')[2]; - // pathname && userState.vaultMap?.has(vaultId); - // }, [pathname, userState.vaultMap]); - /** * Explicitly update selected series on series map changes * */ @@ -777,9 +562,7 @@ const UserProvider = ({ children }: { children: ReactNode }) => { const userActions = { updateSeries, updateAssets, - updateVaults, updateStrategies, - setSelectedVault: useCallback( (vault: IVault | null) => updateState({ type: UserState.SELECTED_VAULT, payload: vault! }), [] From e616fdfd27e3e9e450cc9d07c15c904c7d7ddcc3 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Fri, 12 May 2023 04:57:59 -0700 Subject: [PATCH 2/5] feat: fr vaults to hook --- src/components/PositionAvatar.tsx | 2 +- src/components/positionItems/VaultItem.tsx | 2 +- .../selectors/VaultPositionSelector.tsx | 2 +- src/components/views/Borrow.tsx | 2 +- src/components/views/VaultPosition.tsx | 2 +- src/contexts/types/user.ts | 20 -- .../useAddCollateral/useAddCollateralVR.ts | 2 +- .../actionHooks/useBorrow/useBorrowVR.ts | 2 +- .../useRemoveCollateralVR.ts | 2 +- .../useRepayDebt/useRepayDebtVR.ts | 2 +- src/hooks/entities/useVaults/useVaultsFR.ts | 171 ++++++++++++++++++ .../entities/{ => useVaults}/useVaultsVR.ts | 22 +-- 12 files changed, 191 insertions(+), 40 deletions(-) create mode 100644 src/hooks/entities/useVaults/useVaultsFR.ts rename src/hooks/entities/{ => useVaults}/useVaultsVR.ts (89%) diff --git a/src/components/PositionAvatar.tsx b/src/components/PositionAvatar.tsx index 5dc3ca58d..5a444854e 100644 --- a/src/components/PositionAvatar.tsx +++ b/src/components/PositionAvatar.tsx @@ -7,7 +7,7 @@ import { UserContext } from '../contexts/UserContext'; import { IVault, ISeries, IAsset, IStrategy, ActionType } from '../types'; import Logo from './logos/Logo'; import useVYTokens, { IVYToken } from '../hooks/entities/useVYTokens'; -import useVaultsVR from '../hooks/entities/useVaultsVR'; +import useVaultsVR from '../hooks/entities/useVaults/useVaultsVR'; const Outer = styled(Box)` position: relative; diff --git a/src/components/positionItems/VaultItem.tsx b/src/components/positionItems/VaultItem.tsx index e08b31386..5c5189d89 100644 --- a/src/components/positionItems/VaultItem.tsx +++ b/src/components/positionItems/VaultItem.tsx @@ -14,7 +14,7 @@ import { cleanValue } from '../../utils/appUtils'; import { GA_Event, GA_Properties } from '../../types/analytics'; import useAnalytics from '../../hooks/useAnalytics'; import useAssetPair from '../../hooks/viewHelperHooks/useAssetPair/useAssetPair'; -import useVaultsVR from '../../hooks/entities/useVaultsVR'; +import useVaultsVR from '../../hooks/entities/useVaults/useVaultsVR'; function VaultItem({ vault, index, condensed }: { vault: IVault; index: number; condensed?: boolean }) { const router = useRouter(); diff --git a/src/components/selectors/VaultPositionSelector.tsx b/src/components/selectors/VaultPositionSelector.tsx index e8ccac7ed..b8ba3ac17 100644 --- a/src/components/selectors/VaultPositionSelector.tsx +++ b/src/components/selectors/VaultPositionSelector.tsx @@ -7,7 +7,7 @@ import VaultListItem from '../positionItems/VaultItem'; import ListWrap from '../wraps/ListWrap'; import { SettingsContext } from '../../contexts/SettingsContext'; import useAccountPlus from '../../hooks/useAccountPlus'; -import useVaultsVR from '../../hooks/entities/useVaultsVR'; +import useVaultsVR from '../../hooks/entities/useVaults/useVaultsVR'; interface IVaultFilter { base: IAsset | undefined; diff --git a/src/components/views/Borrow.tsx b/src/components/views/Borrow.tsx index 8e96f4196..6eb2f507f 100644 --- a/src/components/views/Borrow.tsx +++ b/src/components/views/Borrow.tsx @@ -51,7 +51,7 @@ import useAccountPlus from '../../hooks/useAccountPlus'; import VariableRate from '../selectors/VariableRate'; import useBasesVR from '../../hooks/views/useBasesVR'; import useAssetPair from '../../hooks/viewHelperHooks/useAssetPair/useAssetPair'; -import useVaultsVR from '../../hooks/entities/useVaultsVR'; +import useVaultsVR from '../../hooks/entities/useVaults/useVaultsVR'; import { ContractNames } from '../../config/contracts'; import { Cauldron, VRCauldron } from '../../contracts'; diff --git a/src/components/views/VaultPosition.tsx b/src/components/views/VaultPosition.tsx index 82e055694..a978e953c 100644 --- a/src/components/views/VaultPosition.tsx +++ b/src/components/views/VaultPosition.tsx @@ -50,7 +50,7 @@ import { WETH } from '../../config/assets'; import { Address } from '@wagmi/core'; import useAccountPlus from '../../hooks/useAccountPlus'; import useAssetPair from '../../hooks/viewHelperHooks/useAssetPair/useAssetPair'; -import useVaultsVR from '../../hooks/entities/useVaultsVR'; +import useVaultsVR from '../../hooks/entities/useVaults/useVaultsVR'; const VaultPosition = () => { const mobile: boolean = useContext(ResponsiveContext) === 'small'; diff --git a/src/contexts/types/user.ts b/src/contexts/types/user.ts index e592de7be..09302862a 100644 --- a/src/contexts/types/user.ts +++ b/src/contexts/types/user.ts @@ -6,7 +6,6 @@ export interface IUserContext { } export interface IUserContextActions { - updateVaults: (vaultList?: IVault[]) => void; updateSeries: (seriesList: ISeries[]) => void; updateAssets: (assetList: IAsset[]) => void; updateStrategies: (strategyList: IStrategy[]) => void; @@ -24,10 +23,8 @@ export interface IUserContextState { assetMap: Map; seriesMap: Map; - vaultMap: Map; strategyMap: Map; - vaultsLoading: boolean; seriesLoading: boolean; assetsLoading: boolean; strategiesLoading: boolean; @@ -45,7 +42,6 @@ export enum UserState { ASSETS = 'assets', SERIES = 'series', - VAULTS = 'vaults', STRATEGIES = 'strategies', CLEAR_VAULTS = 'clearVaults', @@ -68,11 +64,6 @@ export type UserLoadingAction = { payload: boolean; }; -export type VaultsLoadingAction = { - type: UserState.VAULTS_LOADING; - payload: boolean; -}; - export type SeriesLoadingAction = { type: UserState.SERIES_LOADING; payload: boolean; @@ -102,14 +93,6 @@ export type StrategiesAction = { type: UserState.STRATEGIES; payload: Map; }; -export type VaultsAction = { - type: UserState.VAULTS; - payload: Map; -}; - -export type ClearVaultsAction = { - type: UserState.CLEAR_VAULTS; -}; export type SelectedVaultAction = { type: UserState.SELECTED_VAULT; @@ -143,15 +126,12 @@ export type SelectedVRAction = { export type UserContextAction = | UserLoadingAction - | VaultsLoadingAction | SeriesLoadingAction | AssetsLoadingAction | StrategiesLoadingAction | AssetsAction | SeriesAction | StrategiesAction - | VaultsAction - | ClearVaultsAction | SelectedVaultAction | SelectedSeriesAction | SelectedIlkAction diff --git a/src/hooks/actionHooks/useAddCollateral/useAddCollateralVR.ts b/src/hooks/actionHooks/useAddCollateral/useAddCollateralVR.ts index e55c6380c..28c6f2f0b 100644 --- a/src/hooks/actionHooks/useAddCollateral/useAddCollateralVR.ts +++ b/src/hooks/actionHooks/useAddCollateral/useAddCollateralVR.ts @@ -12,7 +12,7 @@ import useContracts from '../../useContracts'; import useAccountPlus from '../../useAccountPlus'; import { ContractNames } from '../../../config/contracts'; import { mutate } from 'swr'; -import useVaultsVR from '../../entities/useVaultsVR'; +import useVaultsVR from '../../entities/useVaults/useVaultsVR'; import { HistoryContext } from '../../../contexts/HistoryContext'; export const useAddCollateralVR = () => { diff --git a/src/hooks/actionHooks/useBorrow/useBorrowVR.ts b/src/hooks/actionHooks/useBorrow/useBorrowVR.ts index 475e819d8..c4d5bd99d 100644 --- a/src/hooks/actionHooks/useBorrow/useBorrowVR.ts +++ b/src/hooks/actionHooks/useBorrow/useBorrowVR.ts @@ -14,7 +14,7 @@ import useAccountPlus from '../../useAccountPlus'; import { ContractNames } from '../../../config/contracts'; import useAssetPair from '../../viewHelperHooks/useAssetPair/useAssetPair'; import { useSWRConfig } from 'swr'; -import useVaultsVR from '../../entities/useVaultsVR'; +import useVaultsVR from '../../entities/useVaults/useVaultsVR'; import { HistoryContext } from '../../../contexts/HistoryContext'; export const useBorrowVR = () => { diff --git a/src/hooks/actionHooks/useRemoveCollateral/useRemoveCollateralVR.ts b/src/hooks/actionHooks/useRemoveCollateral/useRemoveCollateralVR.ts index 1e21ed577..71148a604 100644 --- a/src/hooks/actionHooks/useRemoveCollateral/useRemoveCollateralVR.ts +++ b/src/hooks/actionHooks/useRemoveCollateral/useRemoveCollateralVR.ts @@ -13,7 +13,7 @@ import useContracts from '../../useContracts'; import useAccountPlus from '../../useAccountPlus'; import { ContractNames } from '../../../config/contracts'; import { mutate } from 'swr'; -import useVaultsVR from '../../entities/useVaultsVR'; +import useVaultsVR from '../../entities/useVaults/useVaultsVR'; export const useRemoveCollateralVR = () => { const { diff --git a/src/hooks/actionHooks/useRepayDebt/useRepayDebtVR.ts b/src/hooks/actionHooks/useRepayDebt/useRepayDebtVR.ts index 42f463344..835189ce5 100644 --- a/src/hooks/actionHooks/useRepayDebt/useRepayDebtVR.ts +++ b/src/hooks/actionHooks/useRepayDebt/useRepayDebtVR.ts @@ -14,7 +14,7 @@ import useChainId from '../../useChainId'; import useAccountPlus from '../../useAccountPlus'; import { ContractNames } from '../../../config/contracts'; import { mutate } from 'swr'; -import useVaultsVR from '../../entities/useVaultsVR'; +import useVaultsVR from '../../entities/useVaults/useVaultsVR'; import { HistoryContext } from '../../../contexts/HistoryContext'; export const useRepayDebtVR = () => { diff --git a/src/hooks/entities/useVaults/useVaultsFR.ts b/src/hooks/entities/useVaults/useVaultsFR.ts new file mode 100644 index 000000000..bbe656a2d --- /dev/null +++ b/src/hooks/entities/useVaults/useVaultsFR.ts @@ -0,0 +1,171 @@ +import useSWR from 'swr'; +import { IVault } from '../../../types'; +import { useCallback, useContext, useMemo } from 'react'; +import contractAddresses, { ContractNames } from '../../../config/contracts'; +import { + Cauldron, + Cauldron__factory, + CompoundMultiOracle__factory, + WitchV2__factory, + Witch__factory, +} from '../../../contracts'; +import useFork from '../../useFork'; +import useDefaultProvider from '../../useDefaultProvider'; +import { MulticallContext } from '../../../contexts/MutlicallContext'; +import { ChainContext } from '../../../contexts/ChainContext'; +import { formatUnits } from 'ethers/lib/utils.js'; +import { ZERO_BN, bytesToBytes32, calcAccruedDebt } from '@yield-protocol/ui-math'; +import { RATE } from '../../../utils/constants'; +import { cleanValue, generateVaultName } from '../../../utils/appUtils'; +import useAccountPlus from '../../useAccountPlus'; +import useChainId from '../../useChainId'; +import { BigNumber } from 'ethers'; +import useTimeTillMaturity from '../../useTimeTillMaturity'; + +const useVaultsFR = () => { + const { address: account } = useAccountPlus(); + const { multicall, forkMulticall } = useContext(MulticallContext); + const { + chainState: { assetRootMap, seriesRootMap }, + } = useContext(ChainContext); + const chainId = useChainId(); + const provider = useDefaultProvider(); + const { provider: forkProvider, useForkedEnv, forkStartBlock, forkUrl } = useFork(); + const { isMature } = useTimeTillMaturity(); + + // cauldron + const cauldronAddr = contractAddresses.addresses.get(chainId)?.get(ContractNames.CAULDRON); + const cauldron = multicall?.wrap(Cauldron__factory.connect(cauldronAddr!, provider)); + const forkCauldron = forkMulticall?.wrap(Cauldron__factory.connect(cauldronAddr!, forkProvider!)); + // cauldron to use when fetching a vault's data + const cauldronToUse = useForkedEnv ? forkCauldron! : cauldron; + + // witch + const witchAddr = contractAddresses.addresses.get(chainId)?.get(ContractNames.WITCHV2); + const witch = multicall?.wrap(WitchV2__factory.connect(witchAddr!, provider)); + const forkWitch = forkMulticall?.wrap(WitchV2__factory.connect(witchAddr!, forkProvider!)); + // witchV2 to use when fetching a vault's data + const witchToUse = useForkedEnv ? forkWitch! : witch; + + // witchV1 + const witchV1Addr = contractAddresses.addresses.get(chainId)?.get(ContractNames.WITCH); + const witchV1 = multicall?.wrap(Witch__factory.connect(witchV1Addr!, provider)); + const forkWitchV1 = forkMulticall?.wrap(Witch__factory.connect(witchV1Addr!, forkProvider!)); + // witch to use when fetching a vault's data + const witchV1ToUse = useForkedEnv ? forkWitchV1! : witchV1; + + const getVaultIds = useCallback( + async (cauldron: Cauldron, fromBlock?: number | string): Promise => { + const builtEvents = await cauldron.queryFilter(cauldron.filters.VaultBuilt(null, account), fromBlock); + const receivedEvents = await cauldron.queryFilter(cauldron.filters.VaultGiven(null, account), fromBlock); + return [...new Set([...builtEvents.map((e) => e.args.vaultId), ...receivedEvents.map((e) => e.args.vaultId)])]; + }, + [account] + ); + + const getVault = useCallback( + async (id: string) => { + if (!cauldronToUse || !witchToUse || !witchV1ToUse || !assetRootMap || !account) return; + + const [[art, ink], [owner, seriesId, ilkId]] = await Promise.all([ + cauldronToUse.balances(id), + cauldronToUse.vaults(id), + ]); + + const ilk = assetRootMap.get(ilkId); + const series = seriesRootMap.get(seriesId); + const base = assetRootMap.get(series?.baseId!); + if (!ilk || !base || !series) return; + + const isVaultMature = isMature(series?.maturity!); + + const liquidationEvents = !useForkedEnv + ? await Promise.all([ + witchV1ToUse.queryFilter(witchV1ToUse.filters.Bought(bytesToBytes32(id, 12), null, null, null)), + witchToUse.queryFilter(witchToUse.filters.Bought(bytesToBytes32(id, 12), null, null, null)), + ]) + : []; + const hasBeenLiquidated = liquidationEvents.flat().length > 0; + + let accruedArt: BigNumber; + let rateAtMaturity: BigNumber; + let rate: BigNumber; + + if (isVaultMature) { + const rateOracleAddr = await cauldronToUse.lendingOracles(base.id); + const RateOracle = CompoundMultiOracle__factory.connect(rateOracleAddr, provider); // using compount multi here, but all rate oracles follow the same func sig methodology + + rateAtMaturity = await cauldronToUse.ratesAtMaturity(seriesId); + [rate] = await RateOracle.peek(bytesToBytes32(base.id, 6), RATE, '0'); + + [accruedArt] = rateAtMaturity.gt(ZERO_BN) + ? calcAccruedDebt(rate, rateAtMaturity, art) + : calcAccruedDebt(rate, rate, art); + } else { + rate = BigNumber.from('1'); + rateAtMaturity = BigNumber.from('1'); + accruedArt = art; + } + + return { + id, + art, + art_: cleanValue(formatUnits(art, base.decimals), base.digitFormat), + ink, + ink_: cleanValue(formatUnits(ink, ilk.decimals), ilk.digitFormat), + owner, + seriesId, + baseId: base.id, + ilkId, + rate, + rate_: formatUnits(rate, 18), + hasBeenLiquidated, + isWitchOwner: witchToUse.address === owner || witchV1ToUse.address === owner, // check if witch is the owner (in liquidation process) + accruedArt, + accruedArt_: cleanValue(formatUnits(accruedArt, base.decimals), base.digitFormat), + isActive: owner.toLowerCase() === account.toLowerCase(), // refreshed in case owner has been updated + displayName: generateVaultName(id), + decimals: base.decimals, + } as IVault; + }, + [account, assetRootMap, cauldronToUse, isMature, provider, seriesRootMap, useForkedEnv, witchToUse, witchV1ToUse] + ); + + const getVaults = useCallback(async () => { + if (!cauldron) return; + + console.log('getting vaults in useVaultsFR'); + + // default provider vault ids + const vaultIds = await getVaultIds(cauldron); + + // fork provider vault ids + const forkVaultIds = useForkedEnv && forkCauldron ? await getVaultIds(forkCauldron, forkStartBlock) : []; + + // both fork and non-fork vault ids + const allIds = [...new Set([...vaultIds, ...forkVaultIds])]; + + const vaults = await allIds.reduce(async (acc, id) => { + const vault = await getVault(id); + return vault ? (await acc).set(id, vault) : await acc; + }, Promise.resolve(new Map())); + + return vaults; + }, [cauldron, forkCauldron, forkStartBlock, getVault, getVaultIds, useForkedEnv]); + + // not adding the contracts as deps because they are causing infinite renders + const key = useMemo( + () => ['vaultsFR', account, forkStartBlock, useForkedEnv, assetRootMap, seriesRootMap, forkUrl], + [account, forkStartBlock, useForkedEnv, assetRootMap, seriesRootMap, forkUrl] + ); + + const { data, error, isLoading, isValidating } = useSWR(key, getVaults, { + revalidateOnFocus: false, + revalidateIfStale: false, + shouldRetryOnError: false, + }); + + return { data, error, isLoading: isLoading || isValidating, key }; +}; + +export default useVaultsFR; diff --git a/src/hooks/entities/useVaultsVR.ts b/src/hooks/entities/useVaults/useVaultsVR.ts similarity index 89% rename from src/hooks/entities/useVaultsVR.ts rename to src/hooks/entities/useVaults/useVaultsVR.ts index 934a688f8..56b9a3257 100644 --- a/src/hooks/entities/useVaultsVR.ts +++ b/src/hooks/entities/useVaults/useVaultsVR.ts @@ -1,18 +1,18 @@ import useSWR from 'swr'; -import { IVault } from '../../types'; +import { IVault } from '../../../types'; import { useCallback, useContext, useMemo } from 'react'; -import contractAddresses, { ContractNames } from '../../config/contracts'; -import { CompoundMultiOracle__factory, VRCauldron, VRCauldron__factory, VRWitch__factory } from '../../contracts'; -import useFork from '../useFork'; -import useDefaultProvider from '../useDefaultProvider'; -import { MulticallContext } from '../../contexts/MutlicallContext'; -import { ChainContext } from '../../contexts/ChainContext'; +import contractAddresses, { ContractNames } from '../../../config/contracts'; +import { CompoundMultiOracle__factory, VRCauldron, VRCauldron__factory, VRWitch__factory } from '../../../contracts'; +import useFork from '../../useFork'; +import useDefaultProvider from '../../useDefaultProvider'; +import { MulticallContext } from '../../../contexts/MutlicallContext'; +import { ChainContext } from '../../../contexts/ChainContext'; import { formatUnits } from 'ethers/lib/utils.js'; import { bytesToBytes32 } from '@yield-protocol/ui-math'; -import { RATE } from '../../utils/constants'; -import { cleanValue, generateVaultName } from '../../utils/appUtils'; -import useAccountPlus from '../useAccountPlus'; -import useChainId from '../useChainId'; +import { RATE } from '../../../utils/constants'; +import { cleanValue, generateVaultName } from '../../../utils/appUtils'; +import useAccountPlus from '../../useAccountPlus'; +import useChainId from '../../useChainId'; const useVaultsVR = () => { const { address: account } = useAccountPlus(); From 92415d36c7e93aee681243da7ae853f164059eb8 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Fri, 12 May 2023 05:02:29 -0700 Subject: [PATCH 3/5] feat: useVaults combo fr and vr --- src/hooks/entities/useVaults/index.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/hooks/entities/useVaults/index.ts diff --git a/src/hooks/entities/useVaults/index.ts b/src/hooks/entities/useVaults/index.ts new file mode 100644 index 000000000..f33cbbdb2 --- /dev/null +++ b/src/hooks/entities/useVaults/index.ts @@ -0,0 +1,15 @@ +import useVaultsFR from './useVaultsFR'; +import useVaultsVR from './useVaultsVR'; + +const useVaults = () => { + const { data: vaultsFR, isLoading: vaultsFRLoading } = useVaultsFR(); + const { data: vaultsVR, isLoading: vaultsVRLoading } = useVaultsVR(); + + return { + data: new Map([...(vaultsFR?.entries() || []), ...(vaultsVR?.entries() || [])]), + isLoading: vaultsFRLoading || vaultsVRLoading, + isLoadingFR: vaultsFRLoading, + isLoadingVR: vaultsVRLoading, + }; +}; +export default useVaults; From 83ea619697bb545be8a3bfb0dab4019bf89fda5e Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Fri, 12 May 2023 05:11:56 -0700 Subject: [PATCH 4/5] fix: memo --- src/hooks/entities/useVaults/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/hooks/entities/useVaults/index.ts b/src/hooks/entities/useVaults/index.ts index f33cbbdb2..eac922a24 100644 --- a/src/hooks/entities/useVaults/index.ts +++ b/src/hooks/entities/useVaults/index.ts @@ -1,3 +1,4 @@ +import { useMemo } from 'react'; import useVaultsFR from './useVaultsFR'; import useVaultsVR from './useVaultsVR'; @@ -6,7 +7,10 @@ const useVaults = () => { const { data: vaultsVR, isLoading: vaultsVRLoading } = useVaultsVR(); return { - data: new Map([...(vaultsFR?.entries() || []), ...(vaultsVR?.entries() || [])]), + data: useMemo( + () => new Map([...(vaultsFR?.entries() || []), ...(vaultsVR?.entries() || [])]), + [vaultsFR, vaultsVR] + ), isLoading: vaultsFRLoading || vaultsVRLoading, isLoadingFR: vaultsFRLoading, isLoadingVR: vaultsVRLoading, From 4e8acf003b5c6e6676ce70865f92989ae2643c95 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Fri, 12 May 2023 05:20:12 -0700 Subject: [PATCH 5/5] feat: use new useVaults hook --- src/components/PositionAvatar.tsx | 10 +++++----- src/components/positionItems/VaultItem.tsx | 8 ++++---- .../selectors/VaultPositionSelector.tsx | 16 +++++++--------- src/components/views/Borrow.tsx | 11 +++++------ src/components/views/VaultPosition.tsx | 19 +++++-------------- src/hooks/entities/useVaults/index.ts | 2 ++ 6 files changed, 28 insertions(+), 38 deletions(-) diff --git a/src/components/PositionAvatar.tsx b/src/components/PositionAvatar.tsx index 5a444854e..8afffec90 100644 --- a/src/components/PositionAvatar.tsx +++ b/src/components/PositionAvatar.tsx @@ -4,10 +4,10 @@ import { FiClock } from 'react-icons/fi'; import { MdAutorenew } from 'react-icons/md'; import styled from 'styled-components'; import { UserContext } from '../contexts/UserContext'; -import { IVault, ISeries, IAsset, IStrategy, ActionType } from '../types'; +import { IVault, ISeries, IStrategy, ActionType } from '../types'; import Logo from './logos/Logo'; import useVYTokens, { IVYToken } from '../hooks/entities/useVYTokens'; -import useVaultsVR from '../hooks/entities/useVaults/useVaultsVR'; +import useVaults from '../hooks/entities/useVaults'; const Outer = styled(Box)` position: relative; @@ -42,11 +42,11 @@ function PositionAvatar({ /* STATE FROM CONTEXT */ const { userState } = useContext(UserContext); - const { assetMap, seriesMap, vaultMap } = userState; - const { data: vaultsVR } = useVaultsVR(); + const { assetMap, seriesMap } = userState; + const { data: vaults } = useVaults(); const base = assetMap.get(position?.baseId!); // same for both series, vaults, and vyTokens - const vault = isVault ? vaultMap.get(position?.id!) || vaultsVR?.get(position?.id!) : undefined; + const vault = isVault ? vaults.get(position?.id!) : undefined; const series = vault ? seriesMap.get(vault.seriesId!) : seriesMap.get(position?.id!); const vyToken = vyTokens?.get(position?.id!); diff --git a/src/components/positionItems/VaultItem.tsx b/src/components/positionItems/VaultItem.tsx index 5c5189d89..de332ce79 100644 --- a/src/components/positionItems/VaultItem.tsx +++ b/src/components/positionItems/VaultItem.tsx @@ -14,14 +14,14 @@ import { cleanValue } from '../../utils/appUtils'; import { GA_Event, GA_Properties } from '../../types/analytics'; import useAnalytics from '../../hooks/useAnalytics'; import useAssetPair from '../../hooks/viewHelperHooks/useAssetPair/useAssetPair'; -import useVaultsVR from '../../hooks/entities/useVaults/useVaultsVR'; +import useVaults from '../../hooks/entities/useVaults'; function VaultItem({ vault, index, condensed }: { vault: IVault; index: number; condensed?: boolean }) { const router = useRouter(); const { logAnalyticsEvent } = useAnalytics(); const { - userState: { seriesMap, vaultsLoading, selectedVault, assetMap }, + userState: { seriesMap, selectedVault, assetMap }, userActions, } = useContext(UserContext); const { setSelectedVault } = userActions; @@ -34,7 +34,7 @@ function VaultItem({ vault, index, condensed }: { vault: IVault; index: number; } as GA_Properties.position_opened); }; - const { isLoading: vaultsLoadingVR } = useVaultsVR(); + const { isLoadingFR: vaultsLoadingFR, isLoadingVR: vaultsLoadingVR } = useVaults(); const vaultBase = assetMap?.get(vault.baseId); const vaultIlk = assetMap?.get(vault.ilkId); const vaultIsVR = !vault.seriesId; @@ -75,7 +75,7 @@ function VaultItem({ vault, index, condensed }: { vault: IVault; index: number; {!vaultIsVR && ( - {(vaultsLoading && vault.id === selectedVault?.id) || !debtInBase_ ? ( + {(vaultsLoadingFR && vault.id === selectedVault?.id) || !debtInBase_ ? ( ) : ( cleanValue(debtInBase_, 2) diff --git a/src/components/selectors/VaultPositionSelector.tsx b/src/components/selectors/VaultPositionSelector.tsx index b8ba3ac17..bfa2d6843 100644 --- a/src/components/selectors/VaultPositionSelector.tsx +++ b/src/components/selectors/VaultPositionSelector.tsx @@ -7,7 +7,7 @@ import VaultListItem from '../positionItems/VaultItem'; import ListWrap from '../wraps/ListWrap'; import { SettingsContext } from '../../contexts/SettingsContext'; import useAccountPlus from '../../hooks/useAccountPlus'; -import useVaultsVR from '../../hooks/entities/useVaults/useVaultsVR'; +import useVaults from '../../hooks/entities/useVaults'; interface IVaultFilter { base: IAsset | undefined; @@ -21,10 +21,10 @@ function VaultPositionSelector(target: any) { settingsState: { dashHideInactiveVaults }, } = useContext(SettingsContext); const { - userState: { vaultMap, selectedSeries, selectedBase }, + userState: { selectedSeries, selectedBase }, } = useContext(UserContext); - const { data: vaultsVR } = useVaultsVR(); + const { data: vaults } = useVaults(); const { address: account } = useAccountPlus(); @@ -37,8 +37,7 @@ function VaultPositionSelector(target: any) { const handleFilter = useCallback( ({ base, series, ilk }: IVaultFilter) => { - if (!vaultMap) return; - const _filteredVaults = Array.from(vaultMap.values()) + const _filteredVaults = Array.from(vaults.values()) .filter((vault) => !dashHideInactiveVaults || vault.isActive) .filter((vault) => (base ? vault.baseId === base.proxyId : true)) .filter((vault) => (series ? vault.seriesId === series.id : true)) @@ -49,13 +48,12 @@ function VaultPositionSelector(target: any) { setFilteredVaults(_filteredVaults); console.log('filteredVaults in VaultPositionSelector.tsx: ', _filteredVaults); }, - [vaultMap, dashHideInactiveVaults] + [vaults, dashHideInactiveVaults] ); /* CHECK the list of current vaults which match the current series/ilk selection */ useEffect(() => { - if (!vaultMap) return; - const _allVaults = [...vaultMap.values(), ...(vaultsVR?.values() || [])] + const _allVaults = [...vaults.values()] // filter out vaults that have same base and ilk (borrow and pool liquidity positions) .filter((vault) => vault.baseId !== vault.ilkId) @@ -73,7 +71,7 @@ function VaultPositionSelector(target: any) { if (selectedBase && selectedSeries) { handleFilter({ base: selectedBase, series: selectedSeries, ilk: undefined }); } - }, [vaultMap, selectedBase, selectedSeries, handleFilter, vaultsVR]); + }, [selectedBase, selectedSeries, handleFilter, vaults]); useEffect(() => { allVaults.length <= 5 && setShowAllVaults(true); diff --git a/src/components/views/Borrow.tsx b/src/components/views/Borrow.tsx index 6eb2f507f..570371158 100644 --- a/src/components/views/Borrow.tsx +++ b/src/components/views/Borrow.tsx @@ -51,9 +51,9 @@ import useAccountPlus from '../../hooks/useAccountPlus'; import VariableRate from '../selectors/VariableRate'; import useBasesVR from '../../hooks/views/useBasesVR'; import useAssetPair from '../../hooks/viewHelperHooks/useAssetPair/useAssetPair'; -import useVaultsVR from '../../hooks/entities/useVaults/useVaultsVR'; import { ContractNames } from '../../config/contracts'; import { Cauldron, VRCauldron } from '../../contracts'; +import useVaults from '../../hooks/entities/useVaults'; const Borrow = () => { const mobile: boolean = useContext(ResponsiveContext) === 'small'; @@ -62,8 +62,7 @@ const Borrow = () => { /* STATE FROM CONTEXT */ const { userState, userActions } = useContext(UserContext); - const { assetMap, vaultMap, vaultsLoading, selectedSeries, selectedIlk, selectedBase, selectedVault, selectedVR } = - userState; + const { assetMap, selectedSeries, selectedIlk, selectedBase, selectedVault, selectedVR } = userState; const { setSelectedIlk } = userActions; const { address: activeAccount } = useAccountPlus(); @@ -91,7 +90,7 @@ const Borrow = () => { const { apr } = useApr(borrowInput, ActionType.BORROW, selectedSeries); const { data: assetPair } = useAssetPair(selectedBase?.id, selectedIlk?.id); const { data: basesVR } = useBasesVR(); - const { data: vaultsVR } = useVaultsVR(); + const { vaultsFR, vaultsVR } = useVaults(); const { collateralizationPercent, @@ -256,7 +255,7 @@ const Borrow = () => { /* CHECK the list of current vaults which match the current series/ilk selection */ // TODO look at moving this to helper hook? useEffect(() => { if (selectedBase && selectedIlk) { - const vaults = [...(selectedVR ? (vaultsVR || []).values() : (vaultMap || []).values())]; + const vaults = [...(selectedVR ? (vaultsVR || []).values() : (vaultsFR || []).values())]; const matchingVaults = vaults.filter( (v) => v.ilkId === selectedIlk.proxyId && @@ -266,7 +265,7 @@ const Borrow = () => { ); setMatchingVaults(matchingVaults); } - }, [vaultMap, selectedBase, selectedIlk, selectedSeries, vaultsVR, selectedVR]); + }, [selectedBase, selectedIlk, selectedSeries, vaultsVR, selectedVR, vaultsFR]); /* handle selected vault */ useEffect(() => { diff --git a/src/components/views/VaultPosition.tsx b/src/components/views/VaultPosition.tsx index a978e953c..478abe67c 100644 --- a/src/components/views/VaultPosition.tsx +++ b/src/components/views/VaultPosition.tsx @@ -50,7 +50,7 @@ import { WETH } from '../../config/assets'; import { Address } from '@wagmi/core'; import useAccountPlus from '../../hooks/useAccountPlus'; import useAssetPair from '../../hooks/viewHelperHooks/useAssetPair/useAssetPair'; -import useVaultsVR from '../../hooks/entities/useVaults/useVaultsVR'; +import useVaults from '../../hooks/entities/useVaults'; const VaultPosition = () => { const mobile: boolean = useContext(ResponsiveContext) === 'small'; @@ -61,14 +61,14 @@ const VaultPosition = () => { /* STATE FROM CONTEXT */ const { userState, userActions } = useContext(UserContext); - const { assetMap, seriesMap, vaultMap, vaultsLoading: vaultsLoadingFR, selectedVR } = userState; + const { assetMap, seriesMap, selectedVR } = userState; const { setSelectedBase, setSelectedIlk, setSelectedSeries, setSelectedVault, setSelectedVR } = userActions; - const { data: vaultsVR, isLoading: vaultsLoadingVR } = useVaultsVR(); + const { data: vaults, isLoadingVR: vaultsLoadingVR, isLoadingFR: vaultsLoadingFR } = useVaults(); const vaultsLoading = selectedVR ? vaultsLoadingVR : vaultsLoadingFR; const { address: account } = useAccountPlus(); - const _selectedVault = vaultMap?.get(idFromUrl as string) || vaultsVR?.get(idFromUrl as string); + const _selectedVault = vaults.get(idFromUrl as string); const vaultBase = assetMap?.get(_selectedVault?.baseId!); const vaultIlk = assetMap?.get(_selectedVault?.ilkId!); @@ -377,16 +377,7 @@ const VaultPosition = () => { _selectedVault && setSelectedBase(_base); _selectedVault && setSelectedIlk(_ilkToUse!); _selectedVault && setSelectedVault(_selectedVault); - }, [ - vaultMap, - _selectedVault, - seriesMap, - assetMap, - setSelectedSeries, - setSelectedBase, - setSelectedIlk, - setSelectedVault, - ]); + }, [_selectedVault, seriesMap, assetMap, setSelectedSeries, setSelectedBase, setSelectedIlk, setSelectedVault]); useEffect(() => { if (_selectedVault && account !== _selectedVault?.owner) router.push(prevLoc); diff --git a/src/hooks/entities/useVaults/index.ts b/src/hooks/entities/useVaults/index.ts index eac922a24..8d8395c23 100644 --- a/src/hooks/entities/useVaults/index.ts +++ b/src/hooks/entities/useVaults/index.ts @@ -11,6 +11,8 @@ const useVaults = () => { () => new Map([...(vaultsFR?.entries() || []), ...(vaultsVR?.entries() || [])]), [vaultsFR, vaultsVR] ), + vaultsVR, + vaultsFR, isLoading: vaultsFRLoading || vaultsVRLoading, isLoadingFR: vaultsFRLoading, isLoadingVR: vaultsVRLoading,