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

[Feature] New pointing system #638

Merged
merged 7 commits into from
Jan 18, 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
2 changes: 1 addition & 1 deletion packages/shared/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hats.finance/shared",
"version": "1.1.39",
"version": "1.1.42",
"description": "",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
70 changes: 67 additions & 3 deletions packages/shared/src/severities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,78 @@ export const DefaultIndexArray = [
0, 10, 20, 70, 150, 200, 250, 300, 400, 500, 600, 1000, 1200, 1400, 1800, 2000, 2500, 3000, 4000, 5000, 5500, 6000, 8000,
];

export const IndexToPointsInfo = {
5: {
severityAllocation: 25,
capPerPoint: undefined,
points: {
type: "range",
value: {
first: 0,
second: 1,
},
},
}, // FVV audit
7: {
severityAllocation: 75,
capPerPoint: 1,
points: {
type: "range",
value: {
first: 1,
second: 2,
},
},
}, // Gas audit
11: {
severityAllocation: 75,
capPerPoint: 1,
points: {
type: "fixed",
value: {
first: 1,
},
},
}, // Low audit
17: {
severityAllocation: 75,
capPerPoint: 1,
points: {
type: "fixed",
value: {
first: 12,
},
},
}, // Med audit
20: {
severityAllocation: 75,
capPerPoint: 1,
points: {
type: "fixed",
value: {
first: 25,
},
},
}, // High audit
};

export const convertVulnerabilitySeverityV1ToV2 = (
severity: IEditedVulnerabilitySeverityV1,
indexArray?: number[]
indexArray?: number[],
isAudit: boolean = false
): IEditedVulnerabilitySeverityV2 => {
const newSeverity = { ...severity } as any;
delete newSeverity.index;

return {
...newSeverity,
percentage: indexArray && indexArray[severity.index] ? indexArray[severity.index] / 100 : NaN,
percentage: isAudit
? (IndexToPointsInfo as any)[severity.index as number]?.severityAllocation ?? undefined
: indexArray && indexArray[severity.index]
? indexArray[severity.index] / 100
: NaN,
points: (IndexToPointsInfo as any)[severity.index as number]?.points ?? { type: "fixed", value: { first: 0 } },
percentageCapPerPoint: (IndexToPointsInfo as any)[severity.index as number]?.capPerPoint ?? undefined,
};
};

Expand Down Expand Up @@ -209,7 +271,9 @@ export const getVulnerabilitySeveritiesTemplate = (

const baseTemplateV2: IVulnerabilitySeveritiesTemplateV2 = {
...(baseTemplate as IVulnerabilitySeveritiesTemplateV2),
severities: templateToUse.severities.map((severity) => convertVulnerabilitySeverityV1ToV2(severity, indexArray)),
severities: templateToUse.severities.map((severity) =>
convertVulnerabilitySeverityV1ToV2(severity, indexArray, useAuditTemplate)
),
};

return baseTemplateV2;
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/types/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export interface IEditedVaultDescriptionV1 extends IBaseEditedVaultDescription {
export interface IEditedVaultDescriptionV2 extends IBaseEditedVaultDescription {
version: "v2";
"vulnerability-severities-spec": IVulnerabilitySeveritiesTemplateV2;
usingPointingSystem?: boolean;
}

export type IEditedVaultDescription = IEditedVaultDescriptionV1 | IEditedVaultDescriptionV2;
Expand Down
5 changes: 4 additions & 1 deletion packages/shared/src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ export interface IVaultDescriptionV1 extends IBaseVaultDescription {
export interface IVaultDescriptionV2 extends IBaseVaultDescription {
version: "v2";
severities: Array<IVulnerabilitySeverityV2>;
usingPointingSystem?: boolean;
}

export type IVaultDescription = IVaultDescriptionV1 | IVaultDescriptionV2;
Expand Down Expand Up @@ -209,8 +210,10 @@ export interface IVulnerabilitySeverityV1 extends IBaseVulnerabilitySeverity {
index: number;
}
export interface IVulnerabilitySeverityV2 extends IBaseVulnerabilitySeverity {
percentage: number; // percentage like 1000 (10%) or 8000 (80%)
percentage: number; // percentage of the whole vault allocated to this severity
capAmount?: number;
percentageCapPerPoint?: number; // Max percentage of the whole vault allocated to each point of this severity
points?: { type: "fixed" | "range"; value: { first: number; second?: number } }; // Only when pointing system is used
}

export type IVulnerabilitySeverity = IVulnerabilitySeverityV1 | IVulnerabilitySeverityV2;
Expand Down
7 changes: 7 additions & 0 deletions packages/shared/src/utils/vaultEditor.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ export const createNewVulnerabilitySeverity = (version: "v1" | "v2"): IEditedVul
return {
...editedVulnerabilitySeverityBase,
percentage: 0,
points: {
type: "fixed",
value: {
first: 0,
},
},
} as IEditedVulnerabilitySeverityV2;
}
};
Expand Down Expand Up @@ -337,6 +343,7 @@ export function editedFormToDescription(editedVaultDescription: IEditedVaultDesc
editedVaultDescription["contracts-covered"]
),
scope: editedVaultDescription.scope,
usingPointingSystem: editedVaultDescription.usingPointingSystem,
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { IVault, IVulnerabilitySeverity, IVulnerabilitySeverityV2 } from "@hats.
import InfoIcon from "@mui/icons-material/InfoOutlined";
import { Pill, VaultNftRewardCard, WithTooltip } from "components";
import { useSeverityRewardInfo } from "hooks/severities/useSeverityRewardInfo";
import millify from "millify";
import { useTranslation } from "react-i18next";
import { formatNumber } from "utils";
import { StyledVaultSeverityRewardCard } from "./styles";
Expand All @@ -20,42 +21,121 @@ export function VaultSeverityRewardCard({ vault, severity, severityIndex, noNft
const tokenSymbol = vault.stakingTokenSymbol;
const severityName = severity?.name.toLowerCase().replace("severity", "") ?? "";
const showCap = vault.version === "v2" && vault.description?.severities.some((sev) => !!sev.capAmount);
const usingPointingSystem = vault.version === "v1" ? false : vault.description?.usingPointingSystem;
const pointsInfo = usingPointingSystem && vault.version === "v2" ? (severity as IVulnerabilitySeverityV2).points : undefined;

const getPointingReward = () => {
if (!usingPointingSystem || vault.version === "v1") return;
const maxRewardInfo = vault.amountsInfo?.maxRewardAmount;
const tokenPrice = vault.amountsInfo?.tokenPriceUsd ?? 0;

const percentageAllocatedToSeverity = (severity as IVulnerabilitySeverityV2).percentage;
const maxPercentageAllocatedToPoint = (severity as IVulnerabilitySeverityV2).percentageCapPerPoint;

// Each point will only have a prize (max prize) if the severity has a cap per point
const prizePerPoint = maxPercentageAllocatedToPoint
? (maxRewardInfo?.tokens ?? 0) * (maxPercentageAllocatedToPoint / 100)
: undefined;
// If fixed points we use the first, if range we use the last (second)
const pointsToUse = (pointsInfo?.type === "fixed" ? pointsInfo?.value.first : pointsInfo?.value.second) ?? 0;

const prizeUsingPoints = prizePerPoint! * pointsToUse;
const prizeUsingWholeAlloc = (maxRewardInfo?.tokens ?? 0) * (percentageAllocatedToSeverity / 100);

const maxPrize = maxPercentageAllocatedToPoint
? prizeUsingWholeAlloc > prizeUsingPoints
? prizeUsingPoints
: prizeUsingWholeAlloc
: prizeUsingWholeAlloc;

return (
<>
<div className="severity-prize">
<>
<div>
<span>
{percentageAllocatedToSeverity}% <span className="tiny">{t("ofTheVault")}</span>
</span>
</div>
<div>
{maxPercentageAllocatedToPoint && prizePerPoint && (
<span className="tiny">
{t("maxPrizePerPointExplanation", {
percentage: maxPercentageAllocatedToPoint,
price: millify(prizePerPoint * tokenPrice),
})}
</span>
)}
</div>
{pointsInfo?.type === "fixed" ? (
<div className="mb-1">
<span>{`${pointsInfo?.value.first} ${pointsInfo?.value.first === 1 ? "point" : "points"}`}</span>
<span className="tiny">&nbsp;{t("perFinding")}&nbsp;</span>
</div>
) : (
<>
<div>
<span className="tiny">&nbsp;{t("from").toLowerCase()}&nbsp;</span>
<span>{`${pointsInfo?.value.first} ${pointsInfo?.value.first === 1 ? "point" : "points"}`}</span>
<span className="tiny">&nbsp;{t("to").toLowerCase()}&nbsp;</span>
<span>{`${pointsInfo?.value.second} ${pointsInfo?.value.second === 1 ? "point" : "points"}`}</span>
<span className="tiny">&nbsp;{t("perFinding")}&nbsp;</span>
</div>
</>
)}
<div className="price">
{t("upTo")} {`$${formatNumber(maxPrize * tokenPrice)}`}
<span className="tiny ml-1">({`${formatNumber(maxPrize, 4)} ${tokenSymbol}`})</span>
</div>
</>
</div>
</>
);
};

return (
<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("ofRewards")}&nbsp;</span>
</div>
<span className="price">
~{`$${formatNumber(rewards.usd)}`}
<span className="tiny ml-1">({`${formatNumber(rewards.tokens, 4)} ${tokenSymbol}`})</span>
</span>
</div>
{showCap && (

{usingPointingSystem && vault.version === "v2" ? (
getPointingReward()
) : (
<>
{(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(rewardsCap.usd)}`}
<span className="tiny ml-1">({`${formatNumber(rewardsCap.tokens, 4)} ${tokenSymbol}`})</span>
</span>
</div>
</WithTooltip>
) : (
<div />
<div className="severity-prize">
<div>
<span>{`${rewardPercentage.toFixed(2)}%`}</span>
<span className="tiny">&nbsp;{t("ofRewards")}&nbsp;</span>
</div>
<span className="price">
~{`$${formatNumber(rewards.usd)}`}
<span className="tiny ml-1">({`${formatNumber(rewards.tokens, 4)} ${tokenSymbol}`})</span>
</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(rewardsCap.usd)}`}
<span className="tiny ml-1">({`${formatNumber(rewardsCap.tokens, 4)} ${tokenSymbol}`})</span>
</span>
</div>
</WithTooltip>
) : (
<div />
)}
</>
)}
</>
)}

{!noNft && (
<div className="severity-nft">
<VaultNftRewardCard vault={vault} severity={severity} type="tiny" />
Expand Down
25 changes: 24 additions & 1 deletion packages/web/src/languages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,24 @@
"invalid-json-file": "Invalid JSON file",
"invalid-csv-file": "Invalid CSV file",
"invalid-file-type": "Invalid file type",
"minVal": "Min {{val}}",
"min0": "Min 0",
"max100": "Max 100",
"max": "Max",
"min": "Min",
"fixed": "Fixed",
"range": "Range",
"sumShouldBe100%": "The sum should be 100%",
"enterValidAmount": "Enter a valid amount",
"lessThanSeverityAllocation": "Less than severity allocation",
"enterAnIntegerAndMoreThanOne": "Enter an integer and minimum 1",
"invalidAmount": "Invalid amount",
"insufficientFunds": "Insufficient funds",
"min-amount": "Min amount is {{amount}}",
"max-amount": "Max amount is {{amount}}",
"lessThanVal": "Less than {{val}}",
"enterValidNumber": "Enter a valid number",
"enterIntegerNumber": "Enter an integer number",
"deposit": "Deposit",
"withdraw": "Withdraw",
"withdrawStatus": "Withdraw Status",
Expand Down Expand Up @@ -117,6 +124,7 @@
"parametersEditable": "Parameters editable",
"multisig": "Multisig",
"contact": "Contact",
"vaultEditorUseNewPointingSystem": "Use new pointing system",
"vaultEditorCommitteeMembersSafeExplanation": "<strong>Fill the information for each committee member and create PGP key-pair on Hats dedicated tool or any other trusted tool. Please be aware this information is only for the details of the vault, but all the members of the multisig will have access to all vault actions.</strong> \n\n <ul><li>The PGP keys will permit decrypt submissions.</li> \n <li>Share this page with committee members to generate their own set of keys.</li> \n <li><strong>Please be aware</strong> you can decrypt submissions on the browser you created the keys on. You can open as many keys as you wish on multiple browser & devices.</li></ul>",
"vaultEditorCommitteeDetailsSafeExplanation": "<strong>Add Safe committee multi-sig address.</strong> \n Don't have a Safe? open one <a href='https://app.safe.global/welcome'>in the Safe web-app</a>. \n\n The committee members of your vault will automatically be added in the next page. \n\n <strong>Committee requirements:</strong> \n <ul><li>Minimum amount of committee members is 3.</li> <li>The minimum amount of signers is 2.</li></ul>",
"vaultEditorSeveritiesExplanation": "<strong>Edit the severities for the vault or use an existing vaults severities as a template.</strong> \n\n <ul><li>The description of the severity level will serve as the primary criteria for determining whether the report aligns with this level of severity.</li> \n <li>The severity pool reward (%) will indicate the allocated percentage from the whole vault. For audit competitions the sum of all the percentages among the severities should be 100%.</li></ul>",
Expand Down Expand Up @@ -609,6 +617,10 @@
"doYouWantToGoToProjectWebsite": "Do you want to go to project website? \n ({{website}})",
"yesGo": "Yes, go",
"ofRewards": "of rewards",
"perFinding": "per finding",
"upTo": "up to",
"maxPrizePerPoint": "Max prize per point",
"ofTheVault": "of the vault",
"maxRewardCap": "Max reward cap",
"maxRewardCapExplanation": "This is the maximum amount that will be paid for a single submission.",
"clearSubmission": "Clear submission",
Expand Down Expand Up @@ -652,6 +664,7 @@
"sortBy": "Sort by",
"connecting": "Connecting",
"disconnecting": "Disconnecting",
"maxPrizePerPointExplanation": "Max. {{percentage}}% of the vault (${{price}}) per point",
"HackerProfile": {
"letsDiveIn": "Let's dive in!",
"createProfile": "Create your white hat profile",
Expand Down Expand Up @@ -1325,7 +1338,7 @@
"nft-animation-placeholder": "NFT Animation URL",
"severities-index-array": "Severities Index array",
"severities-index-array-placeholder": "Severities Index array",
"percentage-bounty": "Severity pool allocation (0%-100%)",
"percentage-bounty": "Severity pool allocation (0.1%-100%)",
"percentage-bounty-placeholder": "Percentage of the vault allocated to the severity",
"addStartAndEndDate": "Add start and end date",
"decryptSubmissions": "Should submissions be decrypted?",
Expand Down Expand Up @@ -1355,6 +1368,16 @@
"contractLinesOfCode-placeholder": "Insert lines of code here",
"maxAmountPerBeneficiary": "Maximum amount per finding (cap)",
"maxAmountPerBeneficiary-placeholder": "Max. amount per finding (in tokens amount)",
"percentageCapPerPoint": "Percentage cap per point (0.1%-100%)",
"percentageCapPerPoint-placeholder": "Max. Percentage of the whole vault cap per point",
"points": "Severity points",
"points-placeholder": "How many points this severity is worth",
"pointig-system-type": "Pointing system type",
"pointig-system-type-placeholder": "Select pointing system type",
"severityPointsFrom": "Severity points (from)",
"severityPointsTo": "Severity points (to)",
"maxPercentagePerPoint": "Max. allocation per point (0.1%-100%)",
"maxPercentagePerPoint-placeholder": "Max. percentage of the whole vault per point",
"review-vault": {
"title": "Review Vault",
"description-1": "In order to submit the form for review by hats team.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ export const VaultAsset = ({ vault }: VaultAssetProps) => {
<div className="row">
<VaultTokenIcon vault={vault} />
<div>
{millify(vault.amountsInfo?.depositedAmount.tokens ?? 0)} {vault.stakingTokenSymbol}
{vault.amountsInfo?.depositedAmount.tokens ?? 0}
{vault.stakingTokenSymbol}
</div>
<div>~${millify(vault.amountsInfo?.depositedAmount.usd ?? 0)}</div>
<div className="action-button">
Expand Down
Loading
Loading