Skip to content

Commit

Permalink
Merge pull request #549 from hats-finance/fix/vault-editor-fixe
Browse files Browse the repository at this point in the history
[Feature] Fixed issues with max rewards + improved rewards section for audits
  • Loading branch information
fonstack authored Sep 26, 2023
2 parents 2ecd573 + 9123729 commit 83a1a2e
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 127 deletions.
2 changes: 1 addition & 1 deletion packages/web/src/components/VaultCard/VaultCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ export const VaultCard = ({
? millify(totalPaidOutOnAudit?.usd ?? 0)
: showIntended
? millify(vault.amountsInfo?.competitionIntendedAmount?.deposited.usd ?? 0)
: millify(vault.amountsInfo?.depositedAmount.usd ?? 0)}
: millify(vault.amountsInfo?.maxRewardAmount.usd ?? 0)}
</h3>
</WithTooltip>
<div className="sub-value">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { IVault, IVulnerabilitySeverity } from "@hats-finance/shared";
import { Pill, VaultNftRewardCard } from "components";
import { IVault, IVulnerabilitySeverity, IVulnerabilitySeverityV2 } from "@hats-finance/shared";
import InfoIcon from "@mui/icons-material/InfoOutlined";
import { Pill, VaultNftRewardCard, WithTooltip } from "components";
import { useSeverityRewardInfo } from "hooks/severities/useSeverityRewardInfo";
import { useTranslation } from "react-i18next";
import { formatNumber } from "utils";
Expand All @@ -14,20 +15,40 @@ interface VaultSeverityRewardCardProps {

export function VaultSeverityRewardCard({ vault, severity, severityIndex, noNft = false }: VaultSeverityRewardCardProps) {
const { t } = useTranslation();
const { rewardPercentage, rewardPrice, rewardColor } = useSeverityRewardInfo(vault, severityIndex);
const { rewardPercentage, rewardPrice, rewardCap, rewardColor } = useSeverityRewardInfo(vault, severityIndex);

const severityName = severity?.name.toLowerCase().replace("severity", "") ?? "";
const showCap = vault.version === "v2" && vault.description?.severities.some((sev) => !!sev.capAmount);

return (
<StyledVaultSeverityRewardCard color={rewardColor} noNft={noNft}>
<Pill isSeverity transparent textColor={rewardColor} text={severityName} />
<StyledVaultSeverityRewardCard columns={2 + (noNft ? 0 : 1) + (showCap ? 1 : 0)} color={rewardColor}>
<div className="severity-name">
<Pill isSeverity transparent textColor={rewardColor} text={severityName} />
</div>
<div className="severity-prize">
<div>
<span>{`${rewardPercentage.toFixed(2)}%`}</span>
<span className="tiny">&nbsp;{t("ofVault")}&nbsp;</span>
<span className="tiny">&nbsp;{t("ofRewards")}&nbsp;</span>
</div>
<span className="price">~{`$${formatNumber(rewardPrice)}`}</span>
</div>
{showCap && (
<>
{(severity as IVulnerabilitySeverityV2).capAmount ? (
<WithTooltip text={t("maxRewardCapExplanation")}>
<div className="severity-prize">
<div className="title-container">
<span className="tiny">{t("maxRewardCap")}</span>
<InfoIcon fontSize="small" />
</div>
<span className="price">~{`$${formatNumber(rewardCap)}`}</span>
</div>
</WithTooltip>
) : (
<div />
)}
</>
)}
{!noNft && (
<div className="severity-nft">
<VaultNftRewardCard vault={vault} severity={severity} type="tiny" />
Expand Down
31 changes: 22 additions & 9 deletions packages/web/src/components/VaultSeverityRewardCard/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import styled, { css } from "styled-components";
import { getSpacing } from "styles";
import { breakpointsDefinition } from "styles/breakpoints.styles";

export const StyledVaultSeverityRewardCard = styled.div<{ color: string; noNft: boolean }>(
({ color, noNft }) => css`
export const StyledVaultSeverityRewardCard = styled.div<{ color: string; columns: number }>(
({ color, columns }) => css`
display: grid;
grid-template-columns: ${noNft ? "3fr 4fr" : "1fr 1fr 1fr"};
grid-template-columns: ${columns === 2 ? "3fr 4fr" : columns === 3 ? "1fr 1fr 1fr" : "1fr 1fr 1fr 1fr"};
align-items: center;
justify-content: space-between;
gap: ${getSpacing(1)};
gap: ${getSpacing(2)};
.severity-name,
.severity-prize,
Expand All @@ -24,11 +24,16 @@ export const StyledVaultSeverityRewardCard = styled.div<{ color: string; noNft:
}
@media (max-width: ${breakpointsDefinition.mediumMobile}) {
grid-template-columns: ${noNft ? "1fr 1fr" : "2fr 1fr 1fr"};
grid-template-columns: ${columns === 2 ? "1fr 1fr" : columns === 3 ? "1fr 1fr 1fr" : "1fr 1fr 1fr 1fr"};
}
@media (max-width: ${breakpointsDefinition.smallMobile}) {
grid-template-columns: ${noNft ? "1fr 1fr" : "4fr 4fr 3fr"};
grid-template-columns: ${columns === 2 ? "1fr" : columns === 3 ? "1fr 1fr" : "1fr 1fr"};
.severity-name {
grid-column-start: 1;
grid-column-end: ${columns};
}
}
.severity-name {
Expand All @@ -39,10 +44,17 @@ export const StyledVaultSeverityRewardCard = styled.div<{ color: string; noNft:
.severity-prize {
font-weight: 700;
flex-direction: column;
align-items: flex-end;
@media (max-width: ${breakpointsDefinition.smallMobile}) {
align-items: center;
}
@media (max-width: ${breakpointsDefinition.mediumMobile}) {
flex-direction: column;
align-items: flex-end;
.title-container {
display: flex;
align-items: center;
gap: ${getSpacing(0.2)};
}
.tiny {
Expand All @@ -51,6 +63,7 @@ export const StyledVaultSeverityRewardCard = styled.div<{ color: string; noNft:
}
.price {
font-size: var(--small);
color: ${color};
font-weight: 700;
}
Expand Down
25 changes: 12 additions & 13 deletions packages/web/src/hooks/severities/useSeverityRewardInfo.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { formatUnits } from "ethers/lib/utils";
import { useVaultsTotalPrices } from "hooks/vaults/useVaultsTotalPrices";
import { IVault } from "types";
import { generateColorsArrayInBetween } from "utils/colors.utils";
Expand All @@ -15,60 +14,60 @@ export const getSeveritiesColorsArray = (vault: IVault | undefined): string[] =>
export function useSeverityRewardInfo(vault: IVault | undefined, severityIndex: number) {
const { totalPrices } = useVaultsTotalPrices(vault ? vault.multipleVaults ?? [vault] : []);

if (!vault || !vault.description) return { rewardPrice: 0, rewardPercentage: 0, rewardColor: INITIAL_SEVERITY_COLOR };
if (!vault || !vault.description)
return { rewardPrice: 0, rewardPercentage: 0, rewardCap: 0, rewardColor: INITIAL_SEVERITY_COLOR };

const isAudit = vault.description && vault.description["project-metadata"].type === "audit";
const showIntendedAmounts = vault.amountsInfo?.showCompetitionIntendedAmount ?? false;
const SEVERITIES_COLORS = getSeveritiesColorsArray(vault);

if (vault.version === "v2") {
const severity = vault.description.severities[severityIndex];
if (!severity) return { rewardPrice: 0, rewardPercentage: 0, rewardColor: INITIAL_SEVERITY_COLOR };
if (!severity) return { rewardPrice: 0, rewardPercentage: 0, rewardCap: 0, rewardColor: INITIAL_SEVERITY_COLOR };

const sumTotalPrices = Object.values(totalPrices).reduce((a, b = 0) => a + b, 0);
// const maxBountyPercentage = Number(vault.maxBounty) / 10000; // Number between 0 and 1;
// TODO: remove this when we have the new vault contract version
const maxBountyPercentage = Number(isAudit ? 10000 : vault.maxBounty) / 10000;
const rewardPercentage = +severity.percentage * maxBountyPercentage;
const rewardPercentage = +severity.percentage;

let rewardPrice: number = 0;
let rewardCap: number = 0;
if (vault.multipleVaults && sumTotalPrices) {
rewardPrice = sumTotalPrices * (rewardPercentage / 100);
} else if (vault.amountsInfo?.tokenPriceUsd) {
rewardPrice =
(showIntendedAmounts
? vault.amountsInfo.competitionIntendedAmount?.deposited.tokens ?? 0
: Number(formatUnits(vault.honeyPotBalance, vault.stakingTokenDecimals))) *
: vault.amountsInfo.maxRewardAmount.tokens) *
(rewardPercentage / 100) *
vault.amountsInfo?.tokenPriceUsd;
rewardCap = (severity.capAmount ?? 0) * vault.amountsInfo?.tokenPriceUsd;
}

const orderedSeverities = vault.description.severities.map((severity) => severity.percentage).sort((a, b) => a - b);
const rewardColor: string = SEVERITIES_COLORS[orderedSeverities.indexOf(severity.percentage) ?? 0];

return { rewardPrice, rewardPercentage, rewardColor };
return { rewardPrice, rewardPercentage, rewardCap, rewardColor };
} else {
const severity = vault.description.severities[severityIndex];
if (!severity) return { rewardPrice: 0, rewardPercentage: 0, rewardColor: INITIAL_SEVERITY_COLOR };
if (!severity) return { rewardPrice: 0, rewardPercentage: 0, rewardCap: 0, rewardColor: INITIAL_SEVERITY_COLOR };

const sumTotalPrices = Object.values(totalPrices).reduce((a, b = 0) => a + b, 0);
const rewardPercentage = (Number(vault.rewardsLevels[severity.index]) / 10000) * 100;

let rewardPrice: number = 0;
let rewardCap: number = 0;
if (vault.multipleVaults && sumTotalPrices) {
rewardPrice = sumTotalPrices * (rewardPercentage / 100);
} else if (vault.amountsInfo?.tokenPriceUsd) {
rewardPrice =
(showIntendedAmounts
? vault.amountsInfo.competitionIntendedAmount?.deposited.tokens ?? 0
: Number(formatUnits(vault.honeyPotBalance, vault.stakingTokenDecimals))) *
: vault.amountsInfo.maxRewardAmount.tokens) *
(rewardPercentage / 100) *
vault.amountsInfo?.tokenPriceUsd;
}

const orderedSeverities = vault.description.severities.map((severity) => severity.index).sort((a, b) => a - b);
const rewardColor: string = SEVERITIES_COLORS[orderedSeverities.indexOf(severity.index) ?? 0];

return { rewardPrice, rewardPercentage, rewardColor };
return { rewardPrice, rewardPercentage, rewardCap, rewardColor };
}
}
33 changes: 25 additions & 8 deletions packages/web/src/hooks/subgraph/vaults/parser.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { formatUnits } from "@ethersproject/units";
import { IMaster, IPayoutGraph, IUserNft, IVault } from "@hats-finance/shared";
import { BigNumber } from "ethers";
import { BigNumber, ethers } from "ethers";
import { appChains } from "settings";

export const parseMasters = (masters: IMaster[], chainId: number) => {
Expand Down Expand Up @@ -49,14 +49,32 @@ export const populateVaultsWithPricing = (vaults: IVault[], tokenPrices: number[
const isTestnet = appChains[vault.chainId].chain.testnet;
const tokenPrice: number = isTestnet ? 1387.65 : (tokenPrices && tokenPrices[vault.stakingToken]) ?? 0;
const depositedAmountTokens = Number(formatUnits(vault.honeyPotBalance, vault.stakingTokenDecimals));
const isAudit = vault.description?.["project-metadata"].type === "audit";

const maxRewardFactor = vault.version === "v1" ? +vault.rewardsLevels[vault.rewardsLevels.length - 1] : +vault.maxBounty;
console.log(vault);

const governanceSplit = BigNumber.from(vault.governanceHatRewardSplit).eq(ethers.constants.MaxUint256)
? vault.master.defaultGovernanceHatRewardSplit
: vault.governanceHatRewardSplit;
const hackerHatsSplit = BigNumber.from(vault.hackerHatRewardSplit).eq(ethers.constants.MaxUint256)
? vault.master.defaultHackerHatRewardSplit
: vault.hackerHatRewardSplit;

// In v2 vaults the split sum (immediate, vested, committee) is 100%. So we need to calculate the split factor to get the correct values.
// In v1 this is not a probem. So the factor is 1.
const splitFactor = vault.version === "v1" ? 1 : (10000 - Number(governanceSplit) - Number(hackerHatsSplit)) / 100 / 100;

const governanceFee = Number(governanceSplit) / 100 / 100;
const committeeFee = (Number(vault.committeeRewardSplit) / 100 / 100) * splitFactor;

const maxRewardFactor = 1 - governanceFee - committeeFee;

return {
...vault,
amountsInfo: {
showCompetitionIntendedAmount:
vault.description?.["project-metadata"].type === "audit" &&
isAudit &&
vault.description &&
vault.description["project-metadata"].starttime &&
vault.description["project-metadata"].starttime > new Date().getTime() / 1000 + 48 * 3600 && // 48 hours
!!vault.description?.["project-metadata"].intendedCompetitionAmount &&
Expand All @@ -69,9 +87,8 @@ export const populateVaultsWithPricing = (vaults: IVault[], tokenPrices: number[
usd: +vault.description?.["project-metadata"].intendedCompetitionAmount * tokenPrice,
},
maxReward: {
tokens: +vault.description?.["project-metadata"].intendedCompetitionAmount * (maxRewardFactor / 100 / 100),
usd:
+vault.description?.["project-metadata"].intendedCompetitionAmount * tokenPrice * (maxRewardFactor / 100 / 100),
tokens: +vault.description?.["project-metadata"].intendedCompetitionAmount * maxRewardFactor,
usd: +vault.description?.["project-metadata"].intendedCompetitionAmount * tokenPrice * maxRewardFactor,
},
}
: undefined,
Expand All @@ -80,8 +97,8 @@ export const populateVaultsWithPricing = (vaults: IVault[], tokenPrices: number[
usd: depositedAmountTokens * tokenPrice,
},
maxRewardAmount: {
tokens: depositedAmountTokens * (maxRewardFactor / 100 / 100),
usd: depositedAmountTokens * tokenPrice * (maxRewardFactor / 100 / 100),
tokens: depositedAmountTokens * maxRewardFactor,
usd: depositedAmountTokens * tokenPrice * maxRewardFactor,
},
},
} as IVault;
Expand Down
3 changes: 3 additions & 0 deletions packages/web/src/languages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,9 @@
"goToProjectWebsite": "Go to project website",
"doYouWantToGoToProjectWebsite": "Do you want to go to project website? \n ({{website}})",
"yesGo": "Yes, go",
"ofRewards": "of rewards",
"maxRewardCap": "Max reward cap",
"maxRewardCapExplanation": "This is the maximum amount that will be paid for a single submission.",
"clearSubmission": "Clear submission",
"clearSubmissionExplanation": "Are you sure you want to clear this submission? \n\n This will remove all the information of the submission.",
"clearForm": "Clear form",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,27 @@ type VaultRewardsSectionProps = {

export const VaultRewardsSection = ({ vault }: VaultRewardsSectionProps) => {
const { t } = useTranslation();
const isAudit = vault.description && vault.description["project-metadata"].type === "audit";

const isAudit = vault.description && vault.description["project-metadata"].type === "audit";
const showIntended = vault.amountsInfo?.showCompetitionIntendedAmount ?? false;

return (
<StyledRewardsSection showIntended={showIntended}>
<StyledRewardsSection showIntended={showIntended} isAudit={!!isAudit}>
<h2>{t("rewards")}</h2>
<div className="rewards-containers mt-4">
<div className="amounts">
<div className="card amount-card">
<h4 className="title">{showIntended ? t("intendedDeposits") : t("totalDeposits")}</h4>
{showIntended ? (
<WithTooltip text={t("intendedValueExplanation")}>
<h4 className="value">~${millify(vault.amountsInfo?.competitionIntendedAmount?.deposited.usd ?? 0)}</h4>
</WithTooltip>
) : (
<h4 className="value">~${millify(vault.amountsInfo?.depositedAmount.usd ?? 0)}</h4>
)}
</div>
{!isAudit && (
<div className="card amount-card">
<h4 className="title">{showIntended ? t("intendedDeposits") : t("totalDeposits")}</h4>
{showIntended ? (
<WithTooltip text={t("intendedValueExplanation")}>
<h4 className="value">~${millify(vault.amountsInfo?.competitionIntendedAmount?.deposited.usd ?? 0)}</h4>
</WithTooltip>
) : (
<h4 className="value">~${millify(vault.amountsInfo?.depositedAmount.usd ?? 0)}</h4>
)}
</div>
)}
<div className="card">
<h4 className="title">{t("assetsInVault")}</h4>
<VaultAssetsPillsList vaultData={vault} />
Expand All @@ -44,25 +46,20 @@ export const VaultRewardsSection = ({ vault }: VaultRewardsSectionProps) => {
<h4 className="value">~${millify(vault.amountsInfo?.competitionIntendedAmount?.maxReward.usd ?? 0)}</h4>
</WithTooltip>
) : (
// TODO: In here should be only the max reward amount, not the deposited amount
// Change this once we have the new contract version
<h4 className="value">
~$
{isAudit
? millify(vault.amountsInfo?.depositedAmount.usd ?? 0)
: millify(vault.amountsInfo?.maxRewardAmount.usd ?? 0)}
</h4>
<h4 className="value">~${millify(vault.amountsInfo?.maxRewardAmount.usd ?? 0)}</h4>
)}
</div>
</div>
<div className="division">
<div className="card">
<h4 className="title">{t("rewardsDivision")}</h4>
<div className="chart-container">
<VaultRewardDivision vault={vault} />
{!isAudit && (
<div className="division">
<div className="card">
<h4 className="title">{t("rewardsDivision")}</h4>
<div className="chart-container">
<VaultRewardDivision vault={vault} />
</div>
</div>
</div>
</div>
)}
<div className="severities-rewards">
<div className="card">
<h4 className="title">{t("severityRewards")}</h4>
Expand Down
Loading

0 comments on commit 83a1a2e

Please sign in to comment.