Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/risk #590

Merged
merged 8 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions apps/common/components/ListHead.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,9 @@ export function ListHead({
const [chain, token, ...rest] = items;
return (
<div className={'mt-4 grid w-full grid-cols-1 md:mt-0'}>
<div className={cl('yearn--table-head-wrapper', wrapperClassName)}>
<p className={'yearn--table-head-label max-w-[32px]'}>{chain.label}</p>

<div className={cl('yearn--table-head-token-section -ml-4', tokenClassName)}>
<div className={cl('mb-2 hidden w-full px-10 md:grid md:grid-cols-12', wrapperClassName)}>
<div className={cl('col-span-4 flex gap-6', tokenClassName)}>
<p className={'yearn--table-head-label max-w-[32px]'}>{chain.label}</p>
<button
onClick={(): void => onSort(token.value, toggleSortDirection(token.value))}
className={'yearn--table-head-label-wrapper group'}>
Expand All @@ -71,7 +70,9 @@ export function ListHead({
</button>
</div>

<div className={cl('yearn--table-head-data-section', dataClassName)}>
<div />

<div className={cl('col-span-7 grid grid-cols-10 gap-x-7 pl-6', dataClassName)}>
{rest.map(
(item, index): ReactElement => (
<button
Expand Down
2 changes: 2 additions & 0 deletions apps/common/components/RenderAmount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ export function RenderAmount(props: TAmount & {shouldHideTooltip?: boolean; shou
!isZero(props.value) &&
((props.value < 0.001 && props.symbol !== 'percent') || (props.value < 0.0001 && props.symbol === 'percent'));

const value = formatTAmount(props);
return (
<span
title={value}
suppressHydrationWarning
className={cl(
shouldShowTooltip
Expand Down
2 changes: 1 addition & 1 deletion apps/vaults-v3/components/VaultChainTag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function VaultChainTag({chainID}: {chainID: number}): ReactElement {
<div className={'w-fit'}>
<div
style={{background: 'linear-gradient(244deg, #7B3FE4 5.89%, #A726C1 94.11%)'}}
className={'rounded-2xl px-3.5 py-1 text-neutral-900'}>
className={'rounded-2xl px-3.5 py-1 text-xs text-neutral-900'}>
{'Polygon PoS'}
</div>
</div>
Expand Down
29 changes: 18 additions & 11 deletions apps/vaults-v3/components/details/VaultDetailsTabsWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {IconLinkOut} from '@yearn-finance/web-lib/icons/IconLinkOut';
import {getNetwork} from '@yearn-finance/web-lib/utils/wagmi/utils';
import {IconChevron} from '@common/icons/IconChevron';

import {VaultRiskInfo} from './tabs/VaultRiskInfo';

import type {ReactElement} from 'react';
import type {TYDaemonVault} from '@yearn-finance/web-lib/utils/schemas/yDaemonVaultsSchemas';

Expand All @@ -25,6 +27,7 @@ type TTabsOptions = {
};
type TTabs = {
hasStrategies: boolean;
hasRisk: boolean;
selectedAboutTabIndex: number;
set_selectedAboutTabIndex: (arg0: number) => void;
};
Expand All @@ -34,22 +37,21 @@ type TExplorerLinkProps = {
currentVaultAddress: string;
};

function Tabs({hasStrategies, selectedAboutTabIndex, set_selectedAboutTabIndex}: TTabs): ReactElement {
function Tabs({hasStrategies, hasRisk, selectedAboutTabIndex, set_selectedAboutTabIndex}: TTabs): ReactElement {
const router = useRouter();

const tabs: TTabsOptions[] = useMemo((): TTabsOptions[] => {
const tabs = [{value: 0, label: 'About', slug: 'about'}];
if (hasStrategies) {
return [
{value: 0, label: 'About', slug: 'about'},
{value: 1, label: 'Vaults', slug: 'vaults'},
{value: 2, label: 'Info', slug: 'info'}
];
tabs.push({value: 1, label: 'Vaults', slug: 'vaults'});
}
tabs.push({value: 2, label: 'Info', slug: 'info'});
if (hasRisk) {
tabs.push({value: 3, label: 'Risk', slug: 'risk'});
}
return [
{value: 0, label: 'About', slug: 'about'},
{value: 2, label: 'Info', slug: 'info'}
];
}, [hasStrategies]);

return tabs;
}, [hasStrategies, hasRisk]);

useEffect((): void => {
const tab = tabs.find((tab): boolean => tab.slug === router.query.tab);
Expand Down Expand Up @@ -212,6 +214,7 @@ export function VaultDetailsTabsWrapper({currentVault}: {currentVault: TYDaemonV
<div className={'relative flex w-full flex-row items-center justify-between px-4 pt-4 md:px-8'}>
<Tabs
hasStrategies={hasStrategies}
hasRisk={true}
selectedAboutTabIndex={selectedAboutTabIndex}
set_selectedAboutTabIndex={set_selectedAboutTabIndex}
/>
Expand All @@ -238,6 +241,10 @@ export function VaultDetailsTabsWrapper({currentVault}: {currentVault: TYDaemonV
<Renderable shouldRender={currentVault && selectedAboutTabIndex === 2}>
<VaultInfo currentVault={currentVault} />
</Renderable>

<Renderable shouldRender={currentVault && selectedAboutTabIndex === 3}>
<VaultRiskInfo currentVault={currentVault} />
</Renderable>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {parseMarkdown} from '@yearn-finance/web-lib/utils/helpers';
import type {ReactElement} from 'react';
import type {TYDaemonVault} from '@yearn-finance/web-lib/utils/schemas/yDaemonVaultsSchemas';

function YearnFeesLineItem({children, label, tooltip}: any): ReactElement {
export function YearnFeesLineItem({children, label, tooltip}: any): ReactElement {
return (
<div className={'flex flex-col space-y-0 md:space-y-2'}>
<p className={'text-xxs text-neutral-600 md:text-xs'}>{label}</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export function VaultDetailsStrategies({currentVault}: {currentVault: TYDaemonVa
}}
items={[
{label: 'Vault', value: 'name', sortable: true, className: 'col-span-2'},
{label: 'Risk Level', value: 'score', sortable: true, className: 'col-span-1'},
{label: 'Est. APY', value: 'estAPY', sortable: true, className: 'col-span-2'},
{label: 'Hist. APY', value: 'APY', sortable: true, className: 'col-span-2'},
{label: 'Available', value: 'available', sortable: true, className: 'col-span-2'},
Expand Down
177 changes: 177 additions & 0 deletions apps/vaults-v3/components/details/tabs/VaultRiskInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import {type ReactElement, useMemo} from 'react';
import {cl} from '@builtbymom/web3/utils';

import type {TYDaemonVault} from '@yearn-finance/web-lib/utils/schemas/yDaemonVaultsSchemas';

export function VaultRiskInfo({currentVault}: {currentVault: TYDaemonVault}): ReactElement {
const hasRiskScore = useMemo(() => {
let sum = 0;
currentVault.info.riskScore?.forEach(score => {
sum += score;
});
return sum;
}, [currentVault.info.riskScore]);

return (
<div className={'grid grid-cols-1 gap-4 p-4 md:grid-cols-12 md:gap-10 md:p-8'}>
<div className={'col-span-12 mt-6 w-full md:mt-0'}>
<div className={'mb-4 md:mb-10'}>
<div
className={cl(
'grid w-full grid-cols-12 items-center gap-6',
hasRiskScore ? 'border-b border-neutral-900/20 pb-10 mb-10' : ''
)}>
<div className={'col-span-10'}>
<b className={'block text-neutral-900'}>{'Risk Level'}</b>
<small className={'mt-1 block w-3/4 text-xs text-neutral-900/40'}>
{
"This is an indicator of the security of the vault, calculated based on multiple factors including the strategy's complexity, exposure to potential losses, and reliance on external protocols. A score of 1 represents the highest security, while 5 indicates the lowest."
}
</small>
</div>
<div className={'col-span-2 flex items-center justify-center font-bold'}>
<span>
<b className={'font-number text-xl text-neutral-900'}>{currentVault.info.riskLevel}</b>
<span className={'text-neutral-900/40'}>{' / 5'}</span>
</span>
</div>
</div>

<div className={cl('grid w-full grid-cols-12 items-center gap-6', hasRiskScore ? '' : 'hidden')}>
<div className={'col-span-10'}>
<p>{'Review'}</p>
<small className={'whitespace-break-spaces text-xs text-neutral-900/40'}>
{
'To have a unified reviewScore for both internal and external strategies, we assume that the strategist writer itself is either a Source of Trust (internal) or not (external). So all internal strategies always includes that 1 additional source of trust in addition. Together with all other Sources of Trust (SoTs) in this list: internal strategist wrote the strategy, peer reviews, expert peer reviews, ySec security reviews and ySec recurring security review. Each item accounts for 1 SoT point, and any combinations of these gives the number of SoTs a strategy has and thus gives the associated review score: \n\t\t1 -> 5 SoT \n\t\t2 -> 4 SoT\n\t\t3 -> 3 SoT\n\t\t4 -> 2 SoT\n\t\t5 -> 1 SoT'
}
</small>
</div>
<div className={'col-span-2 flex items-center justify-center font-bold'}>
<p>{currentVault.info.riskScore[0]}</p>
</div>

<div className={'col-span-10'}>
<p>{'Testing'}</p>
<small className={'whitespace-break-spaces text-xs text-neutral-900/40'}>
{
'The testing coverage of the strategy being evaluated. \n\t\t1 -> 95%+\n\t\t2 -> 90%+\n\t\t3 -> 80%+\n\t\t4 -> 70%+\n\t\t5 -> below 70%'
}
</small>
</div>
<div className={'col-span-2 flex items-center justify-center font-bold'}>
<p>{currentVault.info.riskScore[1]}</p>
</div>

<div className={'col-span-10'}>
<p>{'Complexity'}</p>
<small className={'whitespace-break-spaces text-xs text-neutral-900/40'}>
{
'The sLOC count of the strategy being evaluated. \n\t\t1 -> 0-150 sLOC\n\t\t2 -> 150-300 sLOC\n\t\t3 -> 300-450 sLOC\n\t\t4 -> 450-600 sLOC\n\t\t5 -> 750+ sLOC'
}
</small>
</div>
<div className={'col-span-2 flex items-center justify-center font-bold'}>
<p>{currentVault.info.riskScore[2]}</p>
</div>

<div className={'col-span-10'}>
<p>{'Risk Exposure'}</p>
<small className={'whitespace-break-spaces text-xs text-neutral-900/40'}>
{
'This score aims to find out how much and how often a strategy can be subject to losses. \n\t\t1 -> Strategy has no lossable cases, only gains, up only.\n\t\t2 -> Loss of funds or non recoverable funds up to 0-10% (Example, deposit/withdrawal fees or anything protocol specific)\n\t\t3 -> Loss of funds or non recoverable funds up to 10-15% (Example, Protocol specific IL exposure, very high deposit/withdrawal fees)\n\t\t4 -> Loss of funds or non recoverable funds up to 15-70% (Example, adding liquidity to single sided curve stable pools)\n\t\t5 -> Loss of funds or non recoverable funds up to 70-100% (Example, Leveraging cross assets and got liquidated, adding liquidity to volatile pairs single sided)'
}
</small>
</div>
<div className={'col-span-2 flex items-center justify-center font-bold'}>
<p>{currentVault.info.riskScore[3]}</p>
</div>

<div className={'col-span-10'}>
<p>{'Protocol Integration'}</p>
<small className={'whitespace-break-spaces text-xs text-neutral-900/40'}>
{
'The protocols that are integrated into the strategy that is being evaluated. \n\t\t1 -> Strategy interacts with 1 external protocol\n\t\t2 -> Strategy interacts with 2 external protocols\n\t\t3 -> Strategy interacts with 3 external protocols\n\t\t4 -> Strategy interacts with 4 external protocols\n\t\t5 -> Strategy interacts with 5 external protocols'
}
</small>
</div>
<div className={'col-span-2 flex items-center justify-center font-bold'}>
<p>{currentVault.info.riskScore[4]}</p>
</div>

<div className={'col-span-10'}>
<p>{'Centralization Risk'}</p>
<small className={'whitespace-break-spaces text-xs text-neutral-900/40'}>
{
'The centralization score of the strategy that is being evaluated. \n\t\t1 -> Strategy operates without dependency on any privileged roles, ensuring full permissionlessness.\n\t\t2 -> Strategy has privileged roles but they are not vital for operations and pose minimal risk of rug possibilities\n\t\t3 -> Strategy involves privileged roles but less frequently and with less risk of rug possibilities\n\t\t4 -> Strategy frequently depends on off-chain management but has safeguards against rug possibilities by admins\n\t\t5 -> Strategy heavily relies on off-chain management, potentially exposing user funds to rug possibilities by admins'
}
</small>
</div>
<div className={'col-span-2 flex items-center justify-center font-bold'}>
<p>{currentVault.info.riskScore[5]}</p>
</div>

<div className={'col-span-10'}>
<p>{'External Protocol Audit'}</p>
<small className={'whitespace-break-spaces text-xs text-neutral-900/40'}>
{
'The public audits count of the external protocols. \n\t\t1 -> Audit conducted by 4 or more trusted firm or security researcher conducted.\n\t\t2 -> Audit conducted by 3 trusted firm or security researcher conducted\n\t\t3 -> Audit conducted by 2 trusted firm or security researcher conducted\n\t\t4 -> Audit conducted by 1 trusted firm or security researcher conducted\n\t\t5 -> No audit conducted by a trusted firm or security researcher'
}
</small>
</div>
<div className={'col-span-2 flex items-center justify-center font-bold'}>
<p>{currentVault.info.riskScore[6]}</p>
</div>

<div className={'col-span-10'}>
<p>{'External Protocol Centralisation'}</p>
<small className={'whitespace-break-spaces text-xs text-neutral-900/40'}>
{
"Measurement of the centralization score of the external protocols. \n\t\t1 -> Contracts owner is a multisig with known trusted people with Timelock OR Contracts are governanceless, immutable OR Contracts owner can't do any harm to our strategy by setting parameters in external protocol contracts.\n\t\t2 -> Contracts owner is a multisig with known trusted people\n\t\t3 -> Contracts owner is a multisig with known people but multisig threshold is very low\n\t\t4 -> Contracts owner is a multisig but the addresses are not known/hidden OR Contracts owner can harm our strategy by setting parameters in external protocol contracts up to some degree\n\t\t5 -> Contracts owner is an EOA or a multisig with less than 4 members OR Contracts are not verified OR Contracts owner can harm our strategy completely"
}
</small>
</div>
<div className={'col-span-2 flex items-center justify-center font-bold'}>
<p>{currentVault.info.riskScore[7]}</p>
</div>

<div className={'col-span-10'}>
<p>{'External Protocol TVL'}</p>
<small className={'whitespace-break-spaces text-xs text-neutral-900/40'}>
{
'The active TVL that the external protocol holds. \n\t\t1 -> TVL of $480M or more\n\t\t2 -> TVL between $120M and $480M\n\t\t3 -> TVL between $40M and $120M\n\t\t4 -> TVL between $10M and $40M\n\t\t5 -> TVL of $10M or less'
}
</small>
</div>
<div className={'col-span-2 flex items-center justify-center font-bold'}>
<p>{currentVault.info.riskScore[8]}</p>
</div>

<div className={'col-span-10'}>
<p>{'External Protocol Longevity'}</p>
<small className={'whitespace-break-spaces text-xs text-neutral-900/40'}>
{
'How long the external protocol contracts in scope have been deployed alive. \n\t\t1 -> 24 months or more\n\t\t2 -> Between 18 and 24 months\n\t\t3 -> Between 12 and 18 months\n\t\t4 -> Between 6 and 12 months\n\t\t5 -> Less than 6 months'
}
</small>
</div>
<div className={'col-span-2 flex items-center justify-center font-bold'}>
<p>{currentVault.info.riskScore[9]}</p>
</div>

<div className={'col-span-10'}>
<p>{'External Protocol Type'}</p>
<small className={'whitespace-break-spaces text-xs text-neutral-900/40'}>
{
"This is a rough estimate of evaluating a protocol's purpose. \n\t\t1 -> Blue-chip protocols such as AAVE, Compound, Uniswap, Curve, Convex, and Balancer.\n\t\t2 -> Slightly modified forked blue-chip protocols\n\t\t3 -> AMM lending/borrowing protocols that are not forks of blue-chip protocols, leveraged farming protocols, as well as newly conceptualized protocols\n\t\t4 -> Cross-chain applications, like cross-chain bridges, cross-chain yield aggregators, and cross-chain lending/borrowing protocols\n\t\t5 -> The main expertise of the protocol lies in off-chain operations, such as RWA protocols"
}
</small>
</div>
<div className={'col-span-2 flex items-center justify-center font-bold'}>
<p>{currentVault.info.riskScore[10]}</p>
</div>
</div>
</div>
</div>
</div>
);
}
6 changes: 4 additions & 2 deletions apps/vaults-v3/components/list/VaultsV3ListHead.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export function VaultsV3ListHead({items, sortBy, sortDirection, onSort}: TListHe
)}>
<div
className={cl(
'col-span-5',
'col-span-4',
'flex flex-row items-center justify-between',
'mb-2 py-4 md:mb-0 md:py-0'
)}>
Expand All @@ -77,7 +77,9 @@ export function VaultsV3ListHead({items, sortBy, sortDirection, onSort}: TListHe
</button>
</div>

<div className={cl('col-span-7', 'grid grid-cols-1 md:grid-cols-10', 'gap-0 md:gap-x-7')}>
<div />

<div className={cl('col-span-7 z-10', 'grid grid-cols-2 md:grid-cols-11 gap-1', 'mt-4 md:mt-0')}>
{rest.map(
(item, index): ReactElement => (
<button
Expand Down
Loading
Loading