-
Notifications
You must be signed in to change notification settings - Fork 4
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(beefy): add safety info #621
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { TFunction } from 'i18next' | ||
import { BaseBeefyVault } from './api' | ||
import { getSafety } from './safety' | ||
|
||
const mockT = ((x: string) => x) as TFunction | ||
|
||
describe('safety', () => { | ||
it('returns safety info for known risks', () => { | ||
const safety = getSafety( | ||
{ | ||
risks: [ | ||
'COMPLEXITY_LOW', | ||
'BATTLE_TESTED', | ||
'IL_NONE', | ||
'MCAP_LARGE', | ||
'AUDIT', | ||
'CONTRACTS_VERIFIED', | ||
], | ||
id: 'mock-vault', | ||
} as BaseBeefyVault, | ||
mockT, | ||
) | ||
|
||
expect(safety).toEqual({ | ||
level: 'high', | ||
risks: [ | ||
{ | ||
category: 'beefyRisks.Categry-Beefy', | ||
isPositive: true, | ||
title: 'beefyRisks.Complexity-Low-Titl', | ||
}, | ||
{ | ||
category: 'beefyRisks.Categry-Beefy', | ||
isPositive: true, | ||
title: 'beefyRisks.Testd-Battle-Titl', | ||
}, | ||
{ | ||
category: 'beefyRisks.Categry-Asset', | ||
isPositive: true, | ||
title: 'beefyRisks.IL-None-Titl', | ||
}, | ||
{ | ||
category: 'beefyRisks.Categry-Asset', | ||
isPositive: true, | ||
title: 'beefyRisks.MktCap-Large-Titl', | ||
}, | ||
{ | ||
category: 'beefyRisks.Categry-Platform', | ||
isPositive: true, | ||
title: 'beefyRisks.Platfrm-Audit-Titl', | ||
}, | ||
{ | ||
category: 'beefyRisks.Categry-Platform', | ||
isPositive: true, | ||
title: 'beefyRisks.Platfrm-Verified-Titl', | ||
}, | ||
], | ||
}) | ||
}) | ||
|
||
it('returns undefined if no known risks', () => { | ||
const safety = getSafety( | ||
{ | ||
risks: ['foo', 'bar'] as string[], | ||
id: 'mock-vault', | ||
} as BaseBeefyVault, | ||
mockT, | ||
) | ||
|
||
expect(safety).toBeUndefined() | ||
}) | ||
|
||
it('excludes unknown risks if there are some unknown risks', () => { | ||
const safety = getSafety( | ||
{ | ||
risks: ['foo', 'EXPERIMENTAL_STRAT', 'MCAP_MICRO'] as string[], | ||
id: 'mock-vault', | ||
} as BaseBeefyVault, | ||
mockT, | ||
) | ||
|
||
expect(safety).toEqual({ | ||
level: 'medium', | ||
risks: [ | ||
{ | ||
category: 'beefyRisks.Categry-Beefy', | ||
isPositive: false, | ||
title: 'beefyRisks.Testd-Experimtl-Titl', | ||
}, | ||
{ | ||
category: 'beefyRisks.Categry-Asset', | ||
isPositive: false, | ||
title: 'beefyRisks.MktCap-Micro-Titl', | ||
}, | ||
], | ||
}) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import { TFunction } from 'i18next' | ||
import { Safety } from '../../types/positions' | ||
import { MAX_SCORE, RISKS, CATEGORIES } from './safetyConfig' | ||
import { logger } from '../../log' | ||
import { BaseBeefyVault } from './api' | ||
|
||
// From https://github.com/beefyfinance/beefy-v2/blob/3690e105c4bb98afcf06f9c3e385d13cc23af5cd/src/helpers/safetyScore.tsx | ||
const calcRisk = (risks: string[]) => { | ||
const categories: Record<string, string[]> = {} | ||
for (const c of Object.keys(CATEGORIES)) { | ||
categories[c] = [] | ||
} | ||
|
||
// reverse lookup | ||
risks.forEach((risk) => { | ||
// should never happen with check below, but leaving as is from beefy codebase | ||
if (!(risk in RISKS)) { | ||
return | ||
} | ||
|
||
// should never happen with type safety, but leaving as is from beefy codebase | ||
const cat = RISKS[risk].category | ||
if (!(cat in CATEGORIES)) { | ||
return | ||
} | ||
|
||
categories[cat].push(risk) | ||
}) | ||
|
||
// reduce & clamp | ||
let score = 0 | ||
for (const [category, weight] of Object.entries(CATEGORIES)) { | ||
score += | ||
weight * | ||
Math.min( | ||
1, | ||
categories[category].reduce( | ||
(acc: number, risk: string) => acc + RISKS[risk].score, | ||
0, | ||
), | ||
) | ||
} | ||
|
||
return score | ||
} | ||
|
||
const safetyScore = (risks: string[]) => { | ||
const score = MAX_SCORE * (1 - calcRisk(risks)) | ||
// from https://github.com/beefyfinance/beefy-v2/blob/3690e105c4bb98afcf06f9c3e385d13cc23af5cd/src/components/SafetyScore/SafetyScore.tsx#L27-L29 | ||
return score > 7.5 ? 'high' : score >= 6.4 ? 'medium' : 'low' | ||
} | ||
|
||
export function getSafety( | ||
vault: BaseBeefyVault, | ||
t: TFunction, | ||
): Safety | undefined { | ||
const { risks } = vault | ||
const knownRisks = risks.filter((risk) => !!RISKS[risk]) | ||
|
||
if (knownRisks.length !== risks.length) { | ||
logger.warn({ vault }, 'Beefy vault has unknown risks') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice addition |
||
} | ||
|
||
if (!knownRisks.length) return | ||
return { | ||
level: safetyScore(knownRisks), | ||
risks: knownRisks.map((risk) => { | ||
const { category, title, score } = RISKS[risk] | ||
return { | ||
// from https://github.com/beefyfinance/beefy-v2/blob/3690e105c4bb98afcf06f9c3e385d13cc23af5cd/src/features/vault/components/SafetyCard/SafetyCard.tsx#L39 | ||
// score represents the level of the risk, higher is worse | ||
isPositive: score <= 0, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a bit confusing that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. agree, I added a clarifying comment |
||
title: t(`beefyRisks.${title}`), | ||
category: t(`beefyRisks.${category}`), | ||
} | ||
}), | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does clamp mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's the math.min if the score goes above 1, taken literally from beefy code