From 6115108487a24f5f99e5acbd1244d1267d5a247e Mon Sep 17 00:00:00 2001 From: Carlos Fontes Date: Wed, 23 Oct 2024 12:14:14 +0100 Subject: [PATCH 01/25] chore: changes on dilesInput --- .../FormSupportFilesInput/FormSupportFilesInput.tsx | 5 +++-- .../FormSupportFilesInput/supportedExtensions.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/web/src/components/FormControls/FormSupportFilesInput/FormSupportFilesInput.tsx b/packages/web/src/components/FormControls/FormSupportFilesInput/FormSupportFilesInput.tsx index 0edb63e18..ef002f031 100644 --- a/packages/web/src/components/FormControls/FormSupportFilesInput/FormSupportFilesInput.tsx +++ b/packages/web/src/components/FormControls/FormSupportFilesInput/FormSupportFilesInput.tsx @@ -20,10 +20,11 @@ type FormSupportFilesInputProps = { name: string; onChange: (data: ISavedFile[]) => void; error?: { message?: string; type: string }; + uploadTo?: "db" | "ipfs"; }; export const FormSupportFilesInputComponent = ( - { colorable = false, isDirty = false, name, onChange, label, error, value }: FormSupportFilesInputProps, + { colorable = false, isDirty = false, name, onChange, label, error, value, uploadTo = "db" }: FormSupportFilesInputProps, ref ) => { const { t } = useTranslation(); @@ -50,7 +51,7 @@ export const FormSupportFilesInputComponent = ( return alert(t("invalid-file-type")); } - filesToUploadPromises.push(FilesService.uploadFileToDB(file)); + filesToUploadPromises.push(FilesService.uploadFileToDB(file, uploadTo === "ipfs")); } const uploadedFiles = await Promise.all(filesToUploadPromises); diff --git a/packages/web/src/components/FormControls/FormSupportFilesInput/supportedExtensions.ts b/packages/web/src/components/FormControls/FormSupportFilesInput/supportedExtensions.ts index 6b3206042..531ba226c 100644 --- a/packages/web/src/components/FormControls/FormSupportFilesInput/supportedExtensions.ts +++ b/packages/web/src/components/FormControls/FormSupportFilesInput/supportedExtensions.ts @@ -1 +1 @@ -export const supportedExtensions = ["txt", "sol", "ts", "js"]; +export const supportedExtensions = ["txt", "sol", "ts", "js", ".md", ".json"]; From 5b547a517d329f2eefae067b87114fd52c167e59 Mon Sep 17 00:00:00 2001 From: Carlos Fontes Date: Thu, 24 Oct 2024 14:23:11 +0100 Subject: [PATCH 02/25] chore: minor UI change --- .../FormSupportFilesInput.tsx | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/packages/web/src/components/FormControls/FormSupportFilesInput/FormSupportFilesInput.tsx b/packages/web/src/components/FormControls/FormSupportFilesInput/FormSupportFilesInput.tsx index ef002f031..11038b700 100644 --- a/packages/web/src/components/FormControls/FormSupportFilesInput/FormSupportFilesInput.tsx +++ b/packages/web/src/components/FormControls/FormSupportFilesInput/FormSupportFilesInput.tsx @@ -21,10 +21,21 @@ type FormSupportFilesInputProps = { onChange: (data: ISavedFile[]) => void; error?: { message?: string; type: string }; uploadTo?: "db" | "ipfs"; + noFilesAttachedInfo?: boolean; }; export const FormSupportFilesInputComponent = ( - { colorable = false, isDirty = false, name, onChange, label, error, value, uploadTo = "db" }: FormSupportFilesInputProps, + { + colorable = false, + isDirty = false, + name, + onChange, + label, + error, + value, + uploadTo = "db", + noFilesAttachedInfo, + }: FormSupportFilesInputProps, ref ) => { const { t } = useTranslation(); @@ -77,17 +88,19 @@ export const FormSupportFilesInputComponent = (

{isUploadingFiles ? `${t("uploadingFiles")}...` : label ?? ""}

-
-

{t("filesAttached")}:

-
    - {value?.map((file, idx) => ( -
  • - handleRemoveFile(idx)} /> -

    {file.name}

    -
  • - ))} -
-
+ {!noFilesAttachedInfo && ( +
+

{t("filesAttached")}:

+
    + {value?.map((file, idx) => ( +
  • + handleRemoveFile(idx)} /> +

    {file.name}

    +
  • + ))} +
+
+ )} {error && {error.message}} From 70dc63ec5d57d82777dab4d98ad20e2046d76946 Mon Sep 17 00:00:00 2001 From: Carlos Fontes Date: Thu, 24 Oct 2024 14:23:38 +0100 Subject: [PATCH 03/25] chore: new submission flow --- packages/web/src/components/Button/styles.ts | 2 + packages/web/src/languages/en.json | 19 + .../SubmissionDescriptions.tsx | 366 +++++++++++++++--- .../SubmissionDescriptions/formSchema.ts | 66 +++- .../SubmissionDescriptions/styles.ts | 112 ++++++ .../FormSteps/SubmissionDescriptions/utils.ts | 61 ++- .../SubmissionFormPage/SubmissionFormPage.tsx | 28 +- .../Submissions/SubmissionFormPage/store.ts | 8 +- .../submissionsService.api.ts | 19 +- .../Submissions/SubmissionFormPage/types.ts | 22 +- 10 files changed, 598 insertions(+), 105 deletions(-) diff --git a/packages/web/src/components/Button/styles.ts b/packages/web/src/components/Button/styles.ts index b6b109947..fb2e4a3f0 100644 --- a/packages/web/src/components/Button/styles.ts +++ b/packages/web/src/components/Button/styles.ts @@ -36,6 +36,8 @@ const getVariableByTextColor = (textColor: ButtonProps["textColor"], styleType: return "--primary"; case "error": return "--error-red"; + case "white": + return "--white"; default: if (styleType === "invisible") return "--secondary"; return "--white"; diff --git a/packages/web/src/languages/en.json b/packages/web/src/languages/en.json index a28eb99d4..2ff3df1d4 100644 --- a/packages/web/src/languages/en.json +++ b/packages/web/src/languages/en.json @@ -735,6 +735,9 @@ "labeledAs": "Labeled as", "onlyShowLabeledIssues": "Show only labeled issues", "ghIssue": "GH issue", + "newSubmission": "New issue", + "complementSubmission": "Add Fix & Test to existing issue", + "pathEndsWithFileNameError": "Invalid path (path needs to end with file name)", "MyWallet": { "overview": "Overview", "pointValue": "Point value", @@ -1331,6 +1334,22 @@ "decryptedSubmissionExplanation": "The submission is decrypted and public. This normally happens on Audit Competitions.", "submissionSubmittedViaAuditWizard": "Submission submitted via auditwizard.io", "editSubmission": "Edit submission", + "submissionDescription": "Submission description", + "selectIssueToComplement": "Type the issue title you want to add a Fix & test", + "githubIssue": "GitHub issue", + "selectGithubIssue": "Select GitHub issue", + "addTestFiles": "Add test files", + "addTestFilesExplanation": "The test should fail with the current code base due to the issue and should pass when the issue will be fixed.", + "addFixFiles": "Add fix files", + "addFixFilesExplanation": "The fix should be a valid fix for the issue and the test should pass with the fixed code.", + "addTestPR": "Add test PR", + "addFixPR": "Add fix PR", + "selectTestFiles": "Select test files", + "selectFixFiles": "Select fix files", + "filePath": "File path", + "filePathPlaceholder": "src/path/to/file.sol", + "testNotApplicable": "Test not applicable", + "youNeedUploadAtLeastOneFile": "You need to upload at least one file", "terms": { "bugBounty": { "submissionSection": "

Your submission will be sent to the committee who will process your submission.

The committee of the vault will respond within 24 hours to acknowledge the receipt of submission via the communication channel you provided.

", diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx index f9e8735ba..26cb261af 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx @@ -1,6 +1,7 @@ -import { ISubmissionMessageObject, IVulnerabilitySeverity } from "@hats.finance/shared"; +import { GithubIssue, ISubmissionMessageObject, IVulnerabilitySeverity } from "@hats.finance/shared"; import { yupResolver } from "@hookform/resolvers/yup"; import AddIcon from "@mui/icons-material/AddOutlined"; +import CloseIcon from "@mui/icons-material/CloseOutlined"; import RemoveIcon from "@mui/icons-material/DeleteOutlined"; import { Alert, @@ -14,6 +15,7 @@ import { } from "components"; import download from "downloadjs"; import { getCustomIsDirty, useEnhancedForm } from "hooks/form"; +import { getGithubIssuesFromVault } from "pages/CommitteeTools/SubmissionsTool/submissionsService.api"; import { useContext, useEffect, useState } from "react"; import { Controller, useFieldArray, useWatch } from "react-hook-form"; import { useTranslation } from "react-i18next"; @@ -33,7 +35,19 @@ export function SubmissionDescriptions() { const isAuditSubmission = vault?.description?.["project-metadata"].type === "audit"; const isPrivateAudit = vault?.description?.["project-metadata"].isPrivateAudit; - const { register, handleSubmit, control, reset, setValue } = useEnhancedForm({ + const [vaultGithubIssuesOpts, setVaultGithubIssuesOpts] = useState(); + const [vaultGithubIssues, setVaultGithubIssues] = useState(undefined); + const [isLoadingGH, setIsLoadingGH] = useState(false); + + const { + register, + handleSubmit, + control, + reset, + setValue, + getValues, + formState: { errors }, + } = useEnhancedForm({ resolver: yupResolver(getCreateDescriptionSchema(t)), mode: "onChange", }); @@ -75,6 +89,11 @@ export function SubmissionDescriptions() { if (!vault || !vault.description || !vault.description.severities) return; for (const [idx, description] of controlledDescriptions.entries()) { + if (description.type === "complement") { + if (description.isEncrypted === true) setValue(`descriptions.${idx}.isEncrypted`, false); + continue; + } + const severitySelected = vault.description?.severities && (vault.description.severities as IVulnerabilitySeverity[]).find((sev) => sev.name.toLowerCase() === description.severity); @@ -97,6 +116,27 @@ export function SubmissionDescriptions() { } }, [controlledDescriptions, vault, setValue, isAuditSubmission]); + // Get information from github + const someComplementSubmission = controlledDescriptions.some((desc) => desc.type === "complement"); + useEffect(() => { + if (!someComplementSubmission) return; + if (!vault) return; + if (vaultGithubIssues !== undefined || isLoadingGH) return; + const loadGhIssues = async () => { + // console.log(1); + setIsLoadingGH(true); + const ghIssues = await getGithubIssuesFromVault(vault); + const ghIssuesOpts = ghIssues.map((ghIssue) => ({ + label: `[#${ghIssue.number}] ${ghIssue.title}`, + value: `${ghIssue.number}`, + })); + setVaultGithubIssuesOpts(ghIssuesOpts); + setVaultGithubIssues(ghIssues); + setIsLoadingGH(false); + }; + loadGhIssues(); + }, [vault, vaultGithubIssues, isLoadingGH, someComplementSubmission]); + const handleSaveAndDownloadDescription = async (formData: ISubmissionsDescriptionsData) => { if (!vault) return; if (!submissionData) return alert("Please fill previous steps first."); @@ -148,6 +188,12 @@ export function SubmissionDescriptions() { `${submissionData.project?.projectName}-${new Date().getTime()}.json` ); + console.log({ + submissionMessage, + submissionInfo: JSON.stringify(submissionInfo), + descriptions: formData.descriptions, + }); + setSubmissionData((prev) => { if (!prev) return prev; return { @@ -165,6 +211,226 @@ export function SubmissionDescriptions() { if (!vault) return {t("Submissions.firstYouNeedToSelectAProject")}; + const getNewIssueForm = (submissionDescription: (typeof controlledDescriptions)[number], index: number) => { + return ( + <> +

{t("Submissions.provideExplanation")}

+
+ + ( + (field.name, dirtyFields, defaultValues)} + error={error} + label={t("severity")} + placeholder={t("severityPlaceholder")} + colorable + options={severitiesOptions ?? []} + {...field} + /> + )} + /> +
+ + ( + (field.name, dirtyFields, defaultValues)} + error={error} + colorable + {...field} + /> + )} + /> + + {!submissionDescription.isEncrypted && !allFormDisabled && ( + ( + + )} + /> + )} + + ); + }; + + const getComplementIssueForm = (submissionDescription: (typeof controlledDescriptions)[number], index: number) => { + return ( + <> +

{t("Submissions.selectIssueToComplement")}

+ ( + (field.name, dirtyFields, defaultValues)} + error={error} + label={t("Submissions.githubIssue")} + placeholder={t("Submissions.selectGithubIssue")} + colorable + options={vaultGithubIssuesOpts ?? []} + {...field} + value={field.value ?? ""} + onChange={(a) => { + field.onChange(a); + const ghIssue = vaultGithubIssues?.find((gh) => gh.number === Number(a as string)); + if (ghIssue) setValue(`descriptions.${index}.complementGhIssue`, ghIssue); + }} + /> + )} + /> + + {/* Fix PR section */} + <> +

{t("Submissions.addFixFiles")}:

+

{t("Submissions.addFixFilesExplanation")}

+ { + if (!a?.length) return; + for (const file of a) { + const fixFiles = getValues(`descriptions.${index}.complementFixFiles`); + if (fixFiles.some((f) => f.file.ipfsHash === file.ipfsHash)) continue; + setValue(`descriptions.${index}.complementFixFiles`, [...fixFiles, { file, path: `test/${file.name}` }]); + } + }} + /> + +
+
+ {(submissionDescription.complementFixFiles ?? []).map((item, idx) => ( +
  • +
    + { + const fixFiles = getValues(`descriptions.${index}.complementFixFiles`); + setValue( + `descriptions.${index}.complementFixFiles`, + fixFiles.filter((_, fileIndex) => fileIndex !== idx) + ); + }} + /> +

    {item.file.name}

    +
    + +
    +

    {t("Submissions.filePath")}:

    + +
    +
  • + ))} +
    +
    + + +
    + + {/* Test PR section */} + {!submissionDescription.testNotApplicable && ( + <> +

    {t("Submissions.addTestFiles")}:

    +

    {t("Submissions.addTestFilesExplanation")}

    + { + if (!a?.length) return; + for (const file of a) { + const testFiles = getValues(`descriptions.${index}.complementTestFiles`); + if (testFiles.some((f) => f.file.ipfsHash === file.ipfsHash)) continue; + setValue(`descriptions.${index}.complementTestFiles`, [...testFiles, { file, path: `test/${file.name}` }]); + } + }} + /> + + {/*

    {t("Submissions.filesAttached")}:

    */} +
    +
    + {(submissionDescription.complementTestFiles ?? []).map((item, idx) => ( +
  • +
    + { + const testFiles = getValues(`descriptions.${index}.complementTestFiles`); + setValue( + `descriptions.${index}.complementTestFiles`, + testFiles.filter((_, fileIndex) => fileIndex !== idx) + ); + }} + /> +

    {item.file.name}

    +
    + +
    +

    {t("Submissions.filePath")}:

    + +
    +
  • + ))} +
    +
    + + )} +
    + +
    + + ); + }; + return ( {controlledDescriptions.map((submissionDescription, index) => { @@ -172,73 +438,45 @@ export function SubmissionDescriptions() {

    - {t("issue")} #{index + 1} + {t("submission")} #{index + 1} - - - {submissionDescription.isEncrypted - ? t("Submissions.encryptedSubmission") - : t("Submissions.decryptedSubmission")} - - + {((submissionDescription.type === "new" && submissionDescription.severity) || + submissionDescription.type === "complement") && ( + + + {submissionDescription.isEncrypted + ? t("Submissions.encryptedSubmission") + : t("Submissions.decryptedSubmission")} + + + )}

    -

    {t("Submissions.provideExplanation")}

    -
    - - ( - (field.name, dirtyFields, defaultValues)} - error={error} - label={t("severity")} - placeholder={t("severityPlaceholder")} - colorable - options={severitiesOptions ?? []} - {...field} - /> - )} - /> -
    +
    +
    setValue(`descriptions.${index}.type`, "new")}> +
    +
    +

    {t("newSubmission")}

    +
    +
    - ( - (field.name, dirtyFields, defaultValues)} - error={error} - colorable - {...field} - /> - )} - /> +
    setValue(`descriptions.${index}.type`, "complement")}> +
    +
    +

    {t("complementSubmission")}

    +
    +
    +
    - {!submissionDescription.isEncrypted && !allFormDisabled && ( - ( - - )} - /> - )} + {submissionDescription.type === "new" + ? getNewIssueForm(submissionDescription, index) + : getComplementIssueForm(submissionDescription, index)} {controlledDescriptions.length > 1 && !allFormDisabled && (
    diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts index 8ae22ea9e..2e16fcd3b 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts @@ -5,13 +5,69 @@ export const getCreateDescriptionSchema = (intl: TFunction) => Yup.object().shape({ descriptions: Yup.array().of( Yup.object({ - title: Yup.string() - .min(5, intl("min-characters", { min: 5 })) - .required(intl("required")), - severity: Yup.string().required(intl("required")), + type: Yup.string().required(intl("required")), + + // new fields + title: Yup.string().when("type", (type: "new" | "complement", schema: any) => { + if (type === "complement") return schema; + return schema.min(5, intl("min-characters", { min: 5 })).required(intl("required")); + }), + severity: Yup.string().when("type", (type: "new" | "complement", schema: any) => { + if (type === "complement") return schema; + return schema.required(intl("required")); + }), description: Yup.string() .min(20, intl("min-characters", { min: 20 })) - .required(intl("required")), + .when("type", (type: "new" | "complement", schema: any) => { + if (type === "complement") return schema; + return schema.required(intl("required")); + }), + + // complement fields + testNotApplicable: Yup.boolean(), + complementGhIssueNumber: Yup.string().when("type", (type: "new" | "complement", schema: any) => { + if (type === "new") return schema; + return schema.required(intl("required")); + }), + complementGhIssue: Yup.object().when("type", (type: "new" | "complement", schema: any) => { + if (type === "new") return schema; + return schema.required(intl("required")); + }), + complementFixFiles: Yup.array() + .of( + Yup.object({ + file: Yup.object().required(intl("required")), + path: Yup.string() + .required(intl("required")) + .test("pathEndsWithFileNameError", intl("pathEndsWithFileNameError"), (val, ctx: any) => { + const fileName = ctx.from[0].value.file.name; + return val?.endsWith(fileName) ?? false; + }), + }) + ) + .when("type", (type: "new" | "complement", schema: any) => { + if (type === "new") return schema; + return schema.required(intl("required")).min(1, intl("required")); + }), + complementTestFiles: Yup.array() + .of( + Yup.object({ + file: Yup.object().required(intl("required")), + path: Yup.string() + .required(intl("required")) + .test("pathEndsWithFileNameError", intl("pathEndsWithFileNameError"), (val, ctx: any) => { + const fileName = ctx.from[0].value.file.name; + return val?.endsWith(fileName) ?? false; + }), + }) + ) + .test("min", intl("required"), (val, ctx: any) => { + const type = ctx.from[0].value.type; + const testNotApplicable = ctx.from[0].value.testNotApplicable; + if (type === "new") return true; + if (testNotApplicable) return true; + return (val?.length ?? 0) > 0 ?? false; + }), }) ), }); diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/styles.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/styles.ts index 76388068e..c0ef49b82 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/styles.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/styles.ts @@ -46,6 +46,118 @@ export const StyledSubmissionDescription = styled.div<{ isEncrypted: boolean }>( } } + .options { + display: flex; + gap: ${getSpacing(3)}; + width: 100%; + + .option { + display: flex; + gap: ${getSpacing(2)}; + cursor: pointer; + transition: all 0.2s; + + &:hover { + opacity: 0.8; + } + + .check-circle { + width: ${getSpacing(2.5)}; + height: ${getSpacing(2.5)}; + aspect-ratio: 1; + border-radius: 50%; + border: 2px solid var(--primary); + + &.selected { + position: relative; + + &::after { + content: ""; + width: ${getSpacing(1.2)}; + height: ${getSpacing(1.2)}; + border-radius: 50%; + top: 50%; + left: 50%; + transform: translate(-43%, -50%); + position: absolute; + background-color: var(--primary); + display: block; + } + } + + &.error { + border-color: var(--error-red); + + &::after { + background-color: var(--error-red); + } + } + } + .info { + width: 100%; + display: flex; + flex-direction: column; + gap: ${getSpacing(0.5)}; + } + } + } + + .files-attached-container { + display: flex; + align-items: center; + gap: ${getSpacing(1)}; + margin-top: ${getSpacing(3)}; + + .files { + display: flex; + align-items: center; + gap: ${getSpacing(2)}; + flex-wrap: wrap; + flex-direction: column; + width: 100%; + + li { + list-style: none; + display: flex; + gap: ${getSpacing(5)}; + width: 100%; + align-items: flex-start; + + .file { + display: flex; + align-items: center; + gap: ${getSpacing(1)}; + border: 1px solid var(--primary); + border-radius: 50px; + padding: ${getSpacing(0.5)} ${getSpacing(1)}; + font-size: var(--xxsmall); + margin-top: ${getSpacing(2)}; + + .remove-icon { + cursor: pointer; + transition: 0.1s; + + &:hover { + color: var(--error-red); + } + } + } + + .file-path { + display: flex; + align-items: flex-start; + gap: ${getSpacing(2)}; + width: 100%; + + p { + white-space: nowrap; + margin-top: ${getSpacing(2.5)}; + } + } + } + } + } + .buttons { display: flex; justify-content: flex-end; diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/utils.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/utils.ts index 3ad0cccd1..25198b105 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/utils.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/utils.ts @@ -1,3 +1,4 @@ +import { IPFS_PREFIX } from "constants/constants"; import { BASE_SERVICE_URL } from "settings"; import { ISubmissionData, ISubmissionsDescriptionsData } from "../../types"; @@ -29,10 +30,18 @@ export const getAuditSubmissionTexts = ( ${descriptions .filter((description) => description.isEncrypted) - .map( - (description, idx) => ` + .map((description, idx) => + description.type === "new" + ? ` ## [ISSUE #${idx + 1}]: ${description.title} (${description.severity})\n ${description.description.trim()} +##` + : ` +## [ISSUE #${idx + 1}]: COMPLEMENTARY [Issue #${description.complementGhIssueNumber}] ${description.complementGhIssue?.title}\n\n +### Fix files +${description.complementFixFiles.map((file) => ` - ${file.path} (${IPFS_PREFIX}/${file.file.ipfsHash})`).join("\n")}\n +### Test files +${description.complementTestFiles.map((file) => ` - ${file.path} (${IPFS_PREFIX}/${file.file.ipfsHash})`).join("\n")} ##` ) .join("\n")}`; @@ -44,8 +53,9 @@ ${description.description.trim()} ${descriptions .filter((description) => !description.isEncrypted) - .map( - (description, idx) => ` + .map((description, idx) => + description.type === "new" + ? ` ## [ISSUE #${idx + 1}]: ${description.title} (${description.severity})\n ${description.description.trim()} ${ @@ -53,6 +63,13 @@ ${ ? `**Files:**\n${description.files.map((file) => ` - ${file.name} (${BASE_SERVICE_URL}/files/${file.ipfsHash})`).join("\n")}` : "" } +##` + : ` +## [ISSUE #${idx + 1}]: COMPLEMENTARY [Issue #${description.complementGhIssueNumber}] ${description.complementGhIssue?.title}\n\n +### Fix files +${description.complementFixFiles.map((file) => ` - ${file.path} (${IPFS_PREFIX}/${file.file.ipfsHash})`).join("\n")}\n +### Test files ${description.testNotApplicable ? "(NOT APPLICABLE) " : ""} +${description.complementTestFiles.map((file) => ` - ${file.path} (${IPFS_PREFIX}/${file.file.ipfsHash})`).join("\n")} ##` ) .join("\n")}`; @@ -97,19 +114,35 @@ export const getGithubIssueDescription = ( submissionData: ISubmissionData, description: ISubmissionsDescriptionsData["descriptions"][0] ) => { - return `${submissionData.ref === "audit-wizard" ? "***Submitted via auditwizard.io***\n" : ""} -**Github username:** ${submissionData.contact?.githubUsername ? `@${submissionData.contact?.githubUsername}` : "--"} + if (description.type === "new") { + return `${submissionData.ref === "audit-wizard" ? "***Submitted via auditwizard.io***\n" : ""} + **Github username:** ${submissionData.contact?.githubUsername ? `@${submissionData.contact?.githubUsername}` : "--"} + **Twitter username:** ${submissionData.contact?.twitterUsername ? `${submissionData.contact?.twitterUsername}` : "--"} + **Submission hash (on-chain):** ${submissionData.submissionResult?.transactionHash} + **Severity:** ${description.severity} + + **Description:** + ${description.description.trim()} + + ${ + description.files && description.files.length > 0 + ? `**Files:**\n${description.files + .map((file) => ` - ${file.name} (${BASE_SERVICE_URL}/files/${file.ipfsHash})`) + .join("\n")}` + : "" + } + `; + } + + return `**Github username:** ${submissionData.contact?.githubUsername ? `@${submissionData.contact?.githubUsername}` : "--"} **Twitter username:** ${submissionData.contact?.twitterUsername ? `${submissionData.contact?.twitterUsername}` : "--"} **Submission hash (on-chain):** ${submissionData.submissionResult?.transactionHash} -**Severity:** ${description.severity} **Description:** -${description.description.trim()} - -${ - description.files && description.files.length > 0 - ? `**Files:**\n${description.files.map((file) => ` - ${file.name} (${BASE_SERVICE_URL}/files/${file.ipfsHash})`).join("\n")}` - : "" -} +### Fix files +${description.complementFixFiles.map((file) => ` - ${file.path} (${IPFS_PREFIX}/${file.file.ipfsHash})`).join("\n")}\n +### Test files ${description.testNotApplicable ? "(NOT APPLICABLE) " : ""} +${description.complementTestFiles.map((file) => ` - ${file.path} (${IPFS_PREFIX}/${file.file.ipfsHash})`).join("\n")} +## `; }; diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/SubmissionFormPage.tsx b/packages/web/src/pages/Submissions/SubmissionFormPage/SubmissionFormPage.tsx index 136bb7507..770521e95 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/SubmissionFormPage.tsx +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/SubmissionFormPage.tsx @@ -1,4 +1,3 @@ -import { IVulnerabilitySeverity } from "@hats.finance/shared"; import ErrorIcon from "@mui/icons-material/ErrorOutlineOutlined"; import ClearIcon from "@mui/icons-material/HighlightOffOutlined"; import { Button, Loading, Seo } from "components"; @@ -59,7 +58,7 @@ export const SubmissionFormPage = () => { { title: t("Submissions.termsAndProcess"), component: SubmissionTermsAndProcess, card: SubmissionStep.terms }, { title: t("Submissions.communicationChannel"), component: SubmissionContactInfo, card: SubmissionStep.contact }, { - title: t("Submissions.describeVulnerability"), + title: t("Submissions.submissionDescription"), component: SubmissionDescriptions, card: SubmissionStep.submissionsDescriptions, }, @@ -300,20 +299,17 @@ export const SubmissionFormPage = () => { verified: false, submission: "", submissionMessage: "", - descriptions: auditWizardSubmission.submissionsDescriptions.descriptions.map((desc: any) => { - const severity = (vault.description?.severities as IVulnerabilitySeverity[]).find( - (sev) => - desc.severity.toLowerCase()?.includes(sev.name.toLowerCase()) || - sev.name.toLowerCase()?.includes(desc.severity.toLowerCase()) - ); - return { - title: desc.title, - description: desc.description, - severity: severity?.name.toLowerCase() ?? desc.severity.toLowerCase(), - isEncrypted: !severity?.decryptSubmissions, - files: [], - }; - }), + descriptions: auditWizardSubmission.submissionsDescriptions.descriptions.map((desc: any) => ({ + type: "new", // or "complement" based on your logic + complementTestFiles: [], + complementFixFiles: [], + title: desc.title, + description: desc.description, + severity: desc.severity, + isEncrypted: desc.isEncrypted, + files: desc.files || [], + testNotApplicable: false, + })), }, submissionResult: undefined, })); diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/store.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/store.ts index 303723edf..c977af6ef 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/store.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/store.ts @@ -1,7 +1,7 @@ import { IVault } from "@hats.finance/shared"; import { Dispatch, SetStateAction, createContext } from "react"; import { SUBMISSION_DESCRIPTION_TEMPLATE } from "./FormSteps/SubmissionDescriptions/utils"; -import { ISubmissionData } from "./types"; +import { ISubmissionData, ISubmissionsDescriptionsData } from "./types"; const packageJSON = require("../../../../package.json"); @@ -29,13 +29,17 @@ export const SUBMISSION_INIT_DATA = { submission: "", descriptions: [ { + type: "new", isEncrypted: true, title: "", description: SUBMISSION_DESCRIPTION_TEMPLATE, severity: "", files: [], sessionKey: "" as any, + complementFixFiles: [], + complementTestFiles: [], + testNotApplicable: false, }, ], - }, + } satisfies ISubmissionsDescriptionsData, }; diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/submissionsService.api.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/submissionsService.api.ts index 8344ad3a0..3ff2815fa 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/submissionsService.api.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/submissionsService.api.ts @@ -1,4 +1,4 @@ -import { IVault } from "@hats.finance/shared"; +import { GithubIssue, IVault } from "@hats.finance/shared"; import { axiosClient } from "config/axiosClient"; import { auditWizardVerifyService } from "constants/constants"; import { BASE_SERVICE_URL } from "settings"; @@ -31,13 +31,28 @@ export async function submitVulnerabilitySubmission( createIssueRequests: vault.description?.["project-metadata"].type === "audit" ? submissionData.submissionsDescriptions.descriptions - ?.filter((desc) => !desc.isEncrypted) + ?.filter((desc) => !desc.isEncrypted && desc.type === "new") ?.map((description) => ({ issueTitle: description.title, issueDescription: getGithubIssueDescription(submissionData, description), issueFiles: description.files?.map((file) => file.ipfsHash), })) : [], + createPRsRequests: + vault.description?.["project-metadata"].type === "audit" + ? submissionData.submissionsDescriptions.descriptions + ?.filter((desc) => !desc.isEncrypted && desc.type === "complement") + ?.map((description) => ({ + pullRequestTitle: `Complementary submission for #${description.complementGhIssueNumber}`, + pullRequestDescription: getGithubIssueDescription(submissionData, description), + pullRequestFiles: [...description.complementFixFiles, ...description.complementTestFiles].map((file) => ({ + path: file.path, + fileIpfsHash: file.file.ipfsHash, + })), + githubIssue: description.complementGhIssue as GithubIssue, + githubIssueNumber: Number(description.complementGhIssueNumber), + })) + : [], }; try { diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts index 14309f77d..f2828703a 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts @@ -1,3 +1,4 @@ +import { GithubIssue } from "@hats.finance/shared"; import { ISavedFile } from "components"; import { SessionKey } from "openpgp"; @@ -29,6 +30,16 @@ export interface ISubmissionsDescriptionsData { submission: string; // Submission object ({encrypted: string, decrypted: string}) submissionMessage: string; // It's only for showing on the frontend final step descriptions: { + type: "new" | "complement"; // "new" is for new vulnerabilities, "complement" is for fix/test submissions + + // complement fields + testNotApplicable: boolean; + complementTestFiles: { file: ISavedFile; path: string }[]; + complementFixFiles: { file: ISavedFile; path: string }[]; + complementGhIssueNumber?: string; + complementGhIssue?: GithubIssue; + + // new fields title: string; description: string; severity: string; @@ -81,6 +92,13 @@ export interface ISubmitSubmissionRequest { issueDescription: string; issueFiles: string[]; }[]; + createPRsRequests: { + pullRequestTitle: string; + pullRequestDescription: string; + pullRequestFiles: { path: string; fileIpfsHash: string }[]; + githubIssue: GithubIssue; + githubIssueNumber?: number; + }[]; } export interface IAuditWizardSubmissionData { @@ -119,8 +137,8 @@ export const getCurrentAuditwizardSubmission = ( ...awSubmission.submissionsDescriptions, descriptions: form.submissionsDescriptions?.descriptions.map((d, idx) => ({ - title: d.title, - description: d.description, + title: d.title ?? "", + description: d.description ?? "", severity: awSubmission.submissionsDescriptions.descriptions[idx].severity, })) ?? [], }, From b08b53521d66f5cbf59df5ea241dc5de0fd4c201 Mon Sep 17 00:00:00 2001 From: Carlos Fontes Date: Wed, 30 Oct 2024 14:02:17 +0000 Subject: [PATCH 04/25] chore: advances with claim fix/test --- packages/shared/src/types/bonusSubmissions.ts | 7 + packages/shared/src/types/index.ts | 1 + packages/shared/src/types/payout.ts | 4 + packages/web/src/languages/en.json | 8 + .../SubmissionsTool/submissionsService.api.ts | 6 + .../components/HackerActivity.tsx | 24 +- .../PublicSubmissionCard.tsx | 41 +- .../claimIssuesService.ts | 35 ++ .../components/SplitPointsActions.tsx | 174 ++++++++ .../PublicSubmissionCard/components/styles.ts | 24 ++ .../PublicSubmissionCard/hooks.ts | 40 ++ .../PublicSubmissionCard/styles.ts | 15 +- .../VaultSubmissionsSection.tsx | 2 +- .../pages/Honeypots/VaultDetailsPage/hooks.ts | 8 +- .../savedSubmissionsService.ts | 2 +- .../SubmissionDescriptions.tsx | 370 ++++++++++-------- .../SubmissionDescriptions/formSchema.ts | 1 + .../FormSteps/SubmissionDescriptions/utils.ts | 15 +- .../SubmissionFormPage/SubmissionFormPage.tsx | 8 +- .../Submissions/SubmissionFormPage/store.ts | 1 + .../submissionsService.api.ts | 9 +- .../Submissions/SubmissionFormPage/types.ts | 1 + 22 files changed, 596 insertions(+), 200 deletions(-) create mode 100644 packages/shared/src/types/bonusSubmissions.ts create mode 100644 packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/claimIssuesService.ts create mode 100644 packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/components/SplitPointsActions.tsx create mode 100644 packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/components/styles.ts create mode 100644 packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/hooks.ts diff --git a/packages/shared/src/types/bonusSubmissions.ts b/packages/shared/src/types/bonusSubmissions.ts new file mode 100644 index 000000000..629dd5066 --- /dev/null +++ b/packages/shared/src/types/bonusSubmissions.ts @@ -0,0 +1,7 @@ +export interface IClaimedIssue { + vaultAddress: string; + issueNumber: string; + claimedBy: string; + claimedAt: Date; + expiresAt: Date; +} diff --git a/packages/shared/src/types/index.ts b/packages/shared/src/types/index.ts index 1fe858e3f..07f061ed5 100644 --- a/packages/shared/src/types/index.ts +++ b/packages/shared/src/types/index.ts @@ -4,3 +4,4 @@ export * from "./payout"; export * from "./safe"; export * from "./submissions"; export * from "./profile"; +export * from "./bonusSubmissions"; diff --git a/packages/shared/src/types/payout.ts b/packages/shared/src/types/payout.ts index ac9407c7a..8a9335b84 100644 --- a/packages/shared/src/types/payout.ts +++ b/packages/shared/src/types/payout.ts @@ -49,6 +49,10 @@ export type GithubIssue = { createdBy: number; labels: string[]; validLabels: string[]; + bonusPointsLabels: { + needsFix: boolean; + needsTest: boolean; + }; createdAt: string; body: string; txHash?: string; diff --git a/packages/web/src/languages/en.json b/packages/web/src/languages/en.json index 2ff3df1d4..199daaf65 100644 --- a/packages/web/src/languages/en.json +++ b/packages/web/src/languages/en.json @@ -738,6 +738,12 @@ "newSubmission": "New issue", "complementSubmission": "Add Fix & Test to existing issue", "pathEndsWithFileNameError": "Invalid path (path needs to end with file name)", + "youNeedToConnectYourWallet": "You need to connect your wallet.", + "youAreNotInTopLeaderboardPercentage": "This action is reserved for participants in the top of HATS leaderboard. Improve your ranking to unlock or connect with your profile.", + "claimIssue": "Claim issue", + "claimIssueDescription": "Are you sure you want to claim this issue?", + "claimedByYou": "Claimed by you", + "issueAlreadyClaimed": "This issue has already been claimed.", "MyWallet": { "overview": "Overview", "pointValue": "Point value", @@ -1336,6 +1342,8 @@ "editSubmission": "Edit submission", "submissionDescription": "Submission description", "selectIssueToComplement": "Type the issue title you want to add a Fix & test", + "firstYouNeedToClaimSomeIssues": "First you need to claim fix & test for some issue", + "claimFixAndTest": "Claim fix & test", "githubIssue": "GitHub issue", "selectGithubIssue": "Select GitHub issue", "addTestFiles": "Add test files", diff --git a/packages/web/src/pages/CommitteeTools/SubmissionsTool/submissionsService.api.ts b/packages/web/src/pages/CommitteeTools/SubmissionsTool/submissionsService.api.ts index c163fe1a6..0d1930180 100644 --- a/packages/web/src/pages/CommitteeTools/SubmissionsTool/submissionsService.api.ts +++ b/packages/web/src/pages/CommitteeTools/SubmissionsTool/submissionsService.api.ts @@ -212,6 +212,8 @@ export async function createPayoutFromSubmissions( } export async function getGithubIssuesFromVault(vault: IVault): Promise { + if (!vault) return []; + const extractTxHashFromBody = (issue: GithubIssue): any => { // const txHash = issue.body.match(/(0x[a-fA-F0-9]{64})/)?.[0]; const txHash = issue.body.match(/(\*\*Submission hash \(on-chain\):\*\* (.*)\n)/)?.[2] ?? undefined; @@ -228,6 +230,10 @@ export async function getGithubIssuesFromVault(vault: IVault): Promise severitiesOrder.includes((label.name as string).toLowerCase())) .map((label: any) => (label.name as string).toLowerCase()), + bonusPointsLabels: { + needsFix: issue.labels.some((label: any) => (label.name as string).toLowerCase() === "needs-fix"), + needsTest: issue.labels.some((label: any) => (label.name as string).toLowerCase() === "needs-test"), + }, createdAt: issue.created_at, body: issue.body, txHash: extractTxHashFromBody(issue), diff --git a/packages/web/src/pages/HackerProfile/HackerProfilePage/components/HackerActivity.tsx b/packages/web/src/pages/HackerProfile/HackerProfilePage/components/HackerActivity.tsx index 14fbc8e94..5d09c0e35 100644 --- a/packages/web/src/pages/HackerProfile/HackerProfilePage/components/HackerActivity.tsx +++ b/packages/web/src/pages/HackerProfile/HackerProfilePage/components/HackerActivity.tsx @@ -1,3 +1,4 @@ +import { GithubIssue } from "@hats.finance/shared"; import PrevIcon from "@mui/icons-material/ArrowBackIosNewOutlined"; import NextIcon from "@mui/icons-material/ArrowForwardIosOutlined"; import { Alert, Pill, WithTooltip } from "components"; @@ -5,7 +6,6 @@ import { getSeveritiesColorsArray } from "hooks/severities/useSeverityRewardInfo import useWindowDimensions from "hooks/useWindowDimensions"; import moment from "moment"; import PublicSubmissionCard from "pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/PublicSubmissionCard"; -import { IGithubIssue } from "pages/Honeypots/VaultDetailsPage/types"; import { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { formatNumber, ipfsTransformUri } from "utils"; @@ -153,16 +153,18 @@ export const HackerActivity = ({ activity }: IHackerActivityProps) => {
    {findings?.length === 0 && } {findings?.map((finding, idx) => { - const submission: IGithubIssue = { - _id: finding.id, - createdAt: new Date(+finding.createdAt * 1000), - vaultId: payout.vault!.id, - severity: finding.submissionDataStructure?.severity, - repoName: "", - issueData: { - issueFiles: [], - issueTitle: finding.submissionDataStructure?.title ?? "", - issueDescription: finding.submissionDataStructure?.content ?? "", + const submission: GithubIssue = { + createdAt: new Date(+finding.createdAt * 1000).toISOString(), + body: finding.submissionDataStructure?.content ?? "", + title: finding.submissionDataStructure?.title ?? "", + validLabels: finding.submissionDataStructure?.severity ? [finding.submissionDataStructure.severity] : [], + id: +finding.id, + number: -1, + labels: [], + createdBy: 0, + bonusPointsLabels: { + needsFix: false, + needsTest: false, }, }; diff --git a/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/PublicSubmissionCard.tsx b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/PublicSubmissionCard.tsx index 8d60f48dc..264cac846 100644 --- a/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/PublicSubmissionCard.tsx +++ b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/PublicSubmissionCard.tsx @@ -1,16 +1,15 @@ -import { IVault, IVulnerabilitySeverity, allowedElementsMarkdown } from "@hats.finance/shared"; +import { GithubIssue, IVault, allowedElementsMarkdown, parseSeverityName } from "@hats.finance/shared"; import MDEditor from "@uiw/react-md-editor"; import { Pill } from "components"; -import { getSeveritiesColorsArray } from "hooks/severities/useSeverityRewardInfo"; import moment from "moment"; -import { IGithubIssue } from "pages/Honeypots/VaultDetailsPage/types"; import { useState } from "react"; import { useTranslation } from "react-i18next"; +import { SplitPointsActions } from "./components/SplitPointsActions"; import { StyledPublicSubmissionCard } from "./styles"; type PublicSubmissionCardProps = { vault: IVault; - submission: IGithubIssue; + submission: GithubIssue; }; function PublicSubmissionCard({ vault, submission }: PublicSubmissionCardProps) { @@ -18,28 +17,38 @@ function PublicSubmissionCard({ vault, submission }: PublicSubmissionCardProps) const [isOpen, setIsOpen] = useState(false); - const severityColors = getSeveritiesColorsArray(vault); - const severityIndex = - submission.severity && - vault?.description?.severities.findIndex((sev: IVulnerabilitySeverity) => - sev.name.toLowerCase().includes(submission.severity ?? "") - ); + const showExtraInfo = submission.number !== -1; return ( -
    setIsOpen((prev) => !prev)}> -
    - +
    +
    setIsOpen((prev) => !prev)}> + {submission && submission?.validLabels.length > 0 && ( +
    + {t("labeledAs")}: + {submission.validLabels.map((label) => ( +
    + +
    + ))} +
    + )} +

    {moment(submission.createdAt).format("Do MMM YYYY - hh:mma")}

    +

    + {showExtraInfo ? Issue #{submission.number}: : ""} {submission.title} +

    -

    {moment(submission.createdAt).format("Do MMM YYYY - hh:mma")}

    -

    {submission.issueData.issueTitle}

    + + {showExtraInfo && (submission.bonusPointsLabels.needsFix || submission.bonusPointsLabels.needsTest) && ( + + )}
    diff --git a/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/claimIssuesService.ts b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/claimIssuesService.ts new file mode 100644 index 000000000..9c2e32d79 --- /dev/null +++ b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/claimIssuesService.ts @@ -0,0 +1,35 @@ +import { IClaimedIssue, IVault } from "@hats.finance/shared"; +import { AxiosError } from "axios"; +import { axiosClient } from "config/axiosClient"; +import { BASE_SERVICE_URL } from "settings"; + +/** + * Claims an issue for a vault + */ +export async function claimIssue(vault: IVault, issueNumber: number): Promise { + try { + const response = await axiosClient.post(`${BASE_SERVICE_URL}/submission-bonus-points/claim/${vault.id}`, { + issueNumber, + }); + return response.data.claimedIssue; + } catch (error) { + console.log(error); + throw ((error as AxiosError).response?.data as any)?.error; + } +} + +/** + * Gets claimed issues for a vault + */ +export async function getClaimedIssuesByVault(vault: IVault): Promise { + const response = await axiosClient.get(`${BASE_SERVICE_URL}/submission-bonus-points/claim/${vault.id}`); + return response.data.claimedIssues ?? []; +} + +/** + * Gets claimed issues for a vault and a claimed by + */ +export async function getClaimedIssuesByVaultAndClaimedBy(vault: IVault, claimedBy: string): Promise { + const response = await axiosClient.get(`${BASE_SERVICE_URL}/submission-bonus-points/claim/${vault.id}/${claimedBy}`); + return response.data.claimedIssues ?? []; +} diff --git a/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/components/SplitPointsActions.tsx b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/components/SplitPointsActions.tsx new file mode 100644 index 000000000..b06135ed6 --- /dev/null +++ b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/components/SplitPointsActions.tsx @@ -0,0 +1,174 @@ +import { GithubIssue, IClaimedIssue } from "@hats.finance/shared"; +import { IVault } from "@hats.finance/shared"; +import UploadIcon from "@mui/icons-material/FileUploadOutlined"; +import FlagIcon from "@mui/icons-material/OutlinedFlagOutlined"; +import { Button, HackerProfileImage, Loading, Pill, WithTooltip } from "components"; +import { useSiweAuth } from "hooks/siwe/useSiweAuth"; +import useConfirm from "hooks/useConfirm"; +import moment from "moment"; +import { useProfileByAddress } from "pages/HackerProfile/hooks"; +import { useAllTimeLeaderboard } from "pages/Leaderboard/LeaderboardPage/components/AllTimeLeaderboard/useAllTimeLeaderboard"; +import { useTranslation } from "react-i18next"; +import { IS_PROD, appChains } from "settings"; +import { useAccount, useNetwork } from "wagmi"; +import { useClaimIssue, useClaimedIssuesByVault } from "../hooks"; +import { StyledSplitPointsActions } from "./styles"; + +export const getClaimedBy = (claimedIssue: IClaimedIssue | undefined) => { + if (!claimedIssue) return undefined; + + const isExpired = moment(claimedIssue.expiresAt).isBefore(moment()); + if (isExpired) return undefined; + + return claimedIssue; +}; + +type SplitPointsActionsProps = { + vault: IVault; + submission: GithubIssue; +}; + +export const SplitPointsActions = ({ vault, submission }: SplitPointsActionsProps) => { + const { t } = useTranslation(); + const { address } = useAccount(); + const { chain } = useNetwork(); + const { tryAuthentication } = useSiweAuth(); + const confirm = useConfirm(); + + const { leaderboard } = useAllTimeLeaderboard("all", "streak"); + const { data: profile } = useProfileByAddress(address); + + const connectedChain = chain ? appChains[chain.id] : null; + const isTestnet = !IS_PROD && connectedChain?.chain.testnet; + const TOP_LEADERBOARD_PERCENTAGE = isTestnet ? 1 : 0.2; + const TOP_LEADERBOARD_MIN_REWARDS = isTestnet ? 0 : 5000; + + const isConnected = !!address; + const isProfileCreated = !!profile; + + const { + data: claimedIssues, + isLoading: isLoadingClaimedIssues, + refetch: refetchClaimedIssues, + } = useClaimedIssuesByVault(vault); + const claimIssue = useClaimIssue(); + + const leaderboardInBoundaries = leaderboard + .slice(0, Math.floor(leaderboard.length * TOP_LEADERBOARD_PERCENTAGE)) + .filter((entry) => entry.totalAmount.usd >= TOP_LEADERBOARD_MIN_REWARDS); + const isInLeadearboardBoundaries = leaderboardInBoundaries.find( + (entry) => entry.address.toLowerCase() === address?.toLowerCase() + ); + + const claimInfo = claimedIssues?.find((issue) => +issue.issueNumber === +submission.number); + const claimedByInfo = getClaimedBy(claimInfo); + const { data: claimedByProfile } = useProfileByAddress(claimedByInfo?.claimedBy); + + // const isClaimedByCurrentUser = claimedByInfo?.claimedBy.toLowerCase() === address?.toLowerCase(); + const isClaimedByCurrentUser = false; + + const canExecuteAction = () => { + if (isClaimedByCurrentUser) return { can: true }; + if (claimedByInfo) return { can: false, reason: t("issueAlreadyClaimed") }; + + if (!isConnected) return { can: false, reason: t("youNeedToConnectYourWallet") }; + if (!isInLeadearboardBoundaries || !isProfileCreated) return { can: false, reason: t("youAreNotInTopLeaderboardPercentage") }; + return { can: true }; + }; + + const getActionButtonContent = () => { + if (isClaimedByCurrentUser) + return ( + <> + + Submit Fix & Test + + ); + if (submission.bonusPointsLabels.needsFix && submission.bonusPointsLabels.needsTest) + return ( + <> + + Claim Fix & Test + + ); + if (submission.bonusPointsLabels.needsFix) + return ( + <> + + Claim Fix + + ); + + if (submission.bonusPointsLabels.needsTest) + return ( + <> + + Claim Test + + ); + return ""; + }; + + const getClaimedByInfo = () => { + return ( +
    +

    Issue claimed by:

    +
    +
    + + {claimedByProfile?.username} +
    + +
    +
    + ); + }; + + const handleClaimIssue = async () => { + if (!canExecuteAction().can) return; + + const wantsToClaim = await confirm({ + title: t("claimIssue"), + titleIcon: , + description: t("claimIssueDescription"), + cancelText: t("no"), + confirmText: t("yes"), + }); + + if (!wantsToClaim) return; + + const signedIn = await tryAuthentication(); + if (!signedIn) return; + + await claimIssue.mutateAsync({ vault, issueNumber: submission.number }); + refetchClaimedIssues(); + }; + + if (isLoadingClaimedIssues) return null; + + return ( + + +
    + +
    +
    + + {claimedByInfo &&
    {getClaimedByInfo()}
    } + + {claimIssue.isLoading && } +
    + ); +}; diff --git a/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/components/styles.ts b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/components/styles.ts new file mode 100644 index 000000000..365403535 --- /dev/null +++ b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/components/styles.ts @@ -0,0 +1,24 @@ +import styled from "styled-components"; +import { getSpacing } from "styles"; + +export const StyledSplitPointsActions = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + flex-direction: row-reverse; + margin-top: ${getSpacing(1)}; + + .claimed-by-container { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: ${getSpacing(1)}; + + .claimed-by-info, + .profile-container { + display: flex; + align-items: center; + gap: 0.5rem; + } + } +`; diff --git a/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/hooks.ts b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/hooks.ts new file mode 100644 index 000000000..fb4b45747 --- /dev/null +++ b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/hooks.ts @@ -0,0 +1,40 @@ +import { IClaimedIssue, IVault } from "@hats.finance/shared"; +import { useMutation } from "@tanstack/react-query"; +import { UseMutationResult, useQuery } from "@tanstack/react-query"; +import { claimIssue, getClaimedIssuesByVault, getClaimedIssuesByVaultAndClaimedBy } from "./claimIssuesService"; + +/** + * Gets claimed issues for a vault + */ +export const useClaimedIssuesByVault = (vault?: IVault) => { + return useQuery({ + queryKey: ["claimed-issues-by-vault", vault?.id, vault?.chainId], + queryFn: () => getClaimedIssuesByVault(vault!), + enabled: !!vault, + }); +}; + +/** + * Gets claimed issues for a vault and a claimed by + */ +export const useClaimedIssuesByVaultAndClaimedBy = (vault?: IVault, claimedBy?: string) => { + return useQuery({ + queryKey: ["claimed-issues-by-vault-and-claimed-by", vault?.id, vault?.chainId, claimedBy], + queryFn: () => getClaimedIssuesByVaultAndClaimedBy(vault!, claimedBy!), + enabled: !!vault && !!claimedBy, + }); +}; + +/** + * Upserts a profile + */ +export const useClaimIssue = (): UseMutationResult< + IClaimedIssue | undefined, + string, + { vault: IVault; issueNumber: number }, + unknown +> => { + return useMutation({ + mutationFn: ({ vault, issueNumber }) => claimIssue(vault, issueNumber), + }); +}; diff --git a/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/styles.ts b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/styles.ts index 965585cfe..c7b70effa 100644 --- a/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/styles.ts +++ b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/styles.ts @@ -20,6 +20,12 @@ export const StyledPublicSubmissionCard = styled.div<{ isOpen: boolean }>( border-color: var(--primary); } + .labels { + display: flex; + gap: ${getSpacing(1)}; + align-items: center; + } + .date { position: absolute; top: ${getSpacing(3)}; @@ -28,8 +34,13 @@ export const StyledPublicSubmissionCard = styled.div<{ isOpen: boolean }>( .submission-title { font-size: var(--small); - padding-left: ${getSpacing(1)}; - font-weight: 700; + font-weight: 400; + margin-top: ${getSpacing(1.5)}; + + span { + font-weight: 700; + color: var(--secondary-light); + } } } diff --git a/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/VaultSubmissionsSection.tsx b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/VaultSubmissionsSection.tsx index 0b2d51a53..a8451dc1b 100644 --- a/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/VaultSubmissionsSection.tsx +++ b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/VaultSubmissionsSection.tsx @@ -66,7 +66,7 @@ export const VaultSubmissionsSection = ({ vault }: VaultSubmissionsSectionProps) {!isPrivateAudit && savedSubmissions && savedSubmissions?.length > 0 && (
    {savedSubmissions.map((submission) => ( - + ))}
    )} diff --git a/packages/web/src/pages/Honeypots/VaultDetailsPage/hooks.ts b/packages/web/src/pages/Honeypots/VaultDetailsPage/hooks.ts index f6c543acb..386176bae 100644 --- a/packages/web/src/pages/Honeypots/VaultDetailsPage/hooks.ts +++ b/packages/web/src/pages/Honeypots/VaultDetailsPage/hooks.ts @@ -1,18 +1,18 @@ -import { IVault } from "@hats.finance/shared"; +import { GithubIssue, IVault } from "@hats.finance/shared"; import { UseMutationResult, UseQueryResult, useQuery } from "@tanstack/react-query"; import { AxiosError } from "axios"; +import { getGithubIssuesFromVault } from "pages/CommitteeTools/SubmissionsTool/submissionsService.api"; import { useAccount, useMutation } from "wagmi"; import * as messageSignaturesService from "./messageSignaturesService"; import * as savedSubmissionsService from "./savedSubmissionsService"; -import { IGithubIssue } from "./types"; /** * Returns all saved submissions for a vault */ -export const useSavedSubmissions = (vault: IVault | undefined): UseQueryResult => { +export const useSavedSubmissions = (vault: IVault | undefined): UseQueryResult => { return useQuery({ queryKey: ["github-issues", vault?.id], - queryFn: () => savedSubmissionsService.getSavedSubmissions(vault?.id), + queryFn: () => getGithubIssuesFromVault(vault!), refetchOnWindowFocus: false, enabled: !!vault, }); diff --git a/packages/web/src/pages/Honeypots/VaultDetailsPage/savedSubmissionsService.ts b/packages/web/src/pages/Honeypots/VaultDetailsPage/savedSubmissionsService.ts index e32f47b00..00a085451 100644 --- a/packages/web/src/pages/Honeypots/VaultDetailsPage/savedSubmissionsService.ts +++ b/packages/web/src/pages/Honeypots/VaultDetailsPage/savedSubmissionsService.ts @@ -5,7 +5,7 @@ import { IGithubIssue } from "./types"; /** * Get all saved github issues for a vault */ -export async function getSavedSubmissions(vaultId: string | undefined): Promise { +export async function getGHSubmissions(vaultId: string | undefined): Promise { if (!vaultId) return []; try { diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx index 26cb261af..ad728f62b 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx @@ -3,6 +3,7 @@ import { yupResolver } from "@hookform/resolvers/yup"; import AddIcon from "@mui/icons-material/AddOutlined"; import CloseIcon from "@mui/icons-material/CloseOutlined"; import RemoveIcon from "@mui/icons-material/DeleteOutlined"; +import FlagIcon from "@mui/icons-material/OutlinedFlagOutlined"; import { Alert, Button, @@ -11,14 +12,21 @@ import { FormSelectInput, FormSelectInputOption, FormSupportFilesInput, + Loading, WithTooltip, } from "components"; import download from "downloadjs"; import { getCustomIsDirty, useEnhancedForm } from "hooks/form"; import { getGithubIssuesFromVault } from "pages/CommitteeTools/SubmissionsTool/submissionsService.api"; +import { useProfileByAddress } from "pages/HackerProfile/hooks"; +import { useClaimedIssuesByVaultAndClaimedBy } from "pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/hooks"; +import { HoneypotsRoutePaths } from "pages/Honeypots/router"; import { useContext, useEffect, useState } from "react"; import { Controller, useFieldArray, useWatch } from "react-hook-form"; import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import { slugify } from "utils/slug.utils"; +import { useAccount } from "wagmi"; import { encryptWithHatsKey, encryptWithKeys } from "../../encrypt"; import { SUBMISSION_INIT_DATA, SubmissionFormContext } from "../../store"; import { ISubmissionsDescriptionsData } from "../../types"; @@ -28,6 +36,10 @@ import { getAuditSubmissionTexts, getBountySubmissionTexts } from "./utils"; export function SubmissionDescriptions() { const { t } = useTranslation(); + const { address } = useAccount(); + const navigate = useNavigate(); + + const { data: hackerProfile } = useProfileByAddress(address); const { submissionData, setSubmissionData, vault, allFormDisabled } = useContext(SubmissionFormContext); const [severitiesOptions, setSeveritiesOptions] = useState(); @@ -35,6 +47,9 @@ export function SubmissionDescriptions() { const isAuditSubmission = vault?.description?.["project-metadata"].type === "audit"; const isPrivateAudit = vault?.description?.["project-metadata"].isPrivateAudit; + const { data: claimedIssues, isLoading: isLoadingClaimedIssues } = useClaimedIssuesByVaultAndClaimedBy(vault, address); + console.log(claimedIssues); + const [vaultGithubIssuesOpts, setVaultGithubIssuesOpts] = useState(); const [vaultGithubIssues, setVaultGithubIssues] = useState(undefined); const [isLoadingGH, setIsLoadingGH] = useState(false); @@ -121,21 +136,24 @@ export function SubmissionDescriptions() { useEffect(() => { if (!someComplementSubmission) return; if (!vault) return; + if (!claimedIssues) return; if (vaultGithubIssues !== undefined || isLoadingGH) return; const loadGhIssues = async () => { - // console.log(1); setIsLoadingGH(true); const ghIssues = await getGithubIssuesFromVault(vault); - const ghIssuesOpts = ghIssues.map((ghIssue) => ({ - label: `[#${ghIssue.number}] ${ghIssue.title}`, - value: `${ghIssue.number}`, - })); + const ghIssuesOpts = ghIssues + .filter((ghIssue) => claimedIssues?.some((ci) => +ci.issueNumber === +ghIssue.number)) + .map((ghIssue) => ({ + label: `[#${ghIssue.number}] ${ghIssue.title}`, + value: `${ghIssue.number}`, + })); + setVaultGithubIssuesOpts(ghIssuesOpts); setVaultGithubIssues(ghIssues); setIsLoadingGH(false); }; loadGhIssues(); - }, [vault, vaultGithubIssues, isLoadingGH, someComplementSubmission]); + }, [vault, vaultGithubIssues, isLoadingGH, someComplementSubmission, claimedIssues]); const handleSaveAndDownloadDescription = async (formData: ISubmissionsDescriptionsData) => { if (!vault) return; @@ -158,7 +176,7 @@ export function SubmissionDescriptions() { if (keyOrKeys.length === 0) return alert("This project has no keys to encrypt the description. Please contact HATS team."); const getSubmissionTextsFunction = isAuditSubmission ? getAuditSubmissionTexts : getBountySubmissionTexts; - const submissionTexts = getSubmissionTextsFunction(submissionData, formData.descriptions); + const submissionTexts = getSubmissionTextsFunction(submissionData, formData.descriptions, hackerProfile); const toEncrypt = submissionTexts.toEncrypt; const decrypted = submissionTexts.decrypted; @@ -211,6 +229,13 @@ export function SubmissionDescriptions() { if (!vault) return {t("Submissions.firstYouNeedToSelectAProject")}; + const getSubmissionsRoute = () => { + const mainRoute = `/${isAuditSubmission ? HoneypotsRoutePaths.audits : HoneypotsRoutePaths.bugBounties}`; + const vaultSlug = slugify(vault.description?.["project-metadata"].name ?? ""); + + return `${mainRoute}/${vaultSlug}-${vault.id}/submissions`; + }; + const getNewIssueForm = (submissionDescription: (typeof controlledDescriptions)[number], index: number) => { return ( <> @@ -255,7 +280,7 @@ export function SubmissionDescriptions() { )} /> - {!submissionDescription.isEncrypted && !allFormDisabled && ( + {/* {!submissionDescription.isEncrypted && !allFormDisabled && ( )} /> - )} + )} */} + +

    Does this issue needs a fix?

    +
    +
    setValue(`descriptions.${index}.isFixApplicable`, true)}> +
    +
    +

    Fix is applicable

    +
    +
    + +
    setValue(`descriptions.${index}.isFixApplicable`, false)}> +
    +
    +

    Fix is not applicable

    +
    +
    +
    ); }; @@ -272,161 +314,177 @@ export function SubmissionDescriptions() { return ( <>

    {t("Submissions.selectIssueToComplement")}

    - ( - (field.name, dirtyFields, defaultValues)} - error={error} - label={t("Submissions.githubIssue")} - placeholder={t("Submissions.selectGithubIssue")} - colorable - options={vaultGithubIssuesOpts ?? []} - {...field} - value={field.value ?? ""} - onChange={(a) => { - field.onChange(a); - const ghIssue = vaultGithubIssues?.find((gh) => gh.number === Number(a as string)); - if (ghIssue) setValue(`descriptions.${index}.complementGhIssue`, ghIssue); - }} - /> - )} - /> - - {/* Fix PR section */} - <> -

    {t("Submissions.addFixFiles")}:

    -

    {t("Submissions.addFixFilesExplanation")}

    - { - if (!a?.length) return; - for (const file of a) { - const fixFiles = getValues(`descriptions.${index}.complementFixFiles`); - if (fixFiles.some((f) => f.file.ipfsHash === file.ipfsHash)) continue; - setValue(`descriptions.${index}.complementFixFiles`, [...fixFiles, { file, path: `test/${file.name}` }]); - } - }} - /> - -
    -
    - {(submissionDescription.complementFixFiles ?? []).map((item, idx) => ( -
  • -
    - { - const fixFiles = getValues(`descriptions.${index}.complementFixFiles`); - setValue( - `descriptions.${index}.complementFixFiles`, - fixFiles.filter((_, fileIndex) => fileIndex !== idx) - ); - }} - /> -

    {item.file.name}

    -
    - -
    -

    {t("Submissions.filePath")}:

    - -
    -
  • - ))} -
    -
    - + {(!vaultGithubIssuesOpts || vaultGithubIssuesOpts?.length === 0) && ( + <> + + {t("Submissions.firstYouNeedToClaimSomeIssues")} + -
    + + + )} - {/* Test PR section */} - {!submissionDescription.testNotApplicable && ( + {vaultGithubIssuesOpts && vaultGithubIssuesOpts?.length > 0 && ( <> -

    {t("Submissions.addTestFiles")}:

    -

    {t("Submissions.addTestFilesExplanation")}

    - { - if (!a?.length) return; - for (const file of a) { - const testFiles = getValues(`descriptions.${index}.complementTestFiles`); - if (testFiles.some((f) => f.file.ipfsHash === file.ipfsHash)) continue; - setValue(`descriptions.${index}.complementTestFiles`, [...testFiles, { file, path: `test/${file.name}` }]); - } - }} + ( + (field.name, dirtyFields, defaultValues)} + error={error} + label={t("Submissions.githubIssue")} + placeholder={t("Submissions.selectGithubIssue")} + colorable + options={vaultGithubIssuesOpts ?? []} + {...field} + value={field.value ?? ""} + onChange={(a) => { + field.onChange(a); + const ghIssue = vaultGithubIssues?.find((gh) => gh.number === Number(a as string)); + if (ghIssue) setValue(`descriptions.${index}.complementGhIssue`, ghIssue); + }} + /> + )} /> - {/*

    {t("Submissions.filesAttached")}:

    */} -
    -
    - {(submissionDescription.complementTestFiles ?? []).map((item, idx) => ( -
  • -
    - { - const testFiles = getValues(`descriptions.${index}.complementTestFiles`); - setValue( - `descriptions.${index}.complementTestFiles`, - testFiles.filter((_, fileIndex) => fileIndex !== idx) - ); - }} - /> -

    {item.file.name}

    -
    - -
    -

    {t("Submissions.filePath")}:

    - -
    -
  • - ))} + {/* Fix PR section */} + <> +

    {t("Submissions.addFixFiles")}:

    +

    {t("Submissions.addFixFilesExplanation")}

    + { + if (!a?.length) return; + for (const file of a) { + const fixFiles = getValues(`descriptions.${index}.complementFixFiles`); + if (fixFiles.some((f) => f.file.ipfsHash === file.ipfsHash)) continue; + setValue(`descriptions.${index}.complementFixFiles`, [...fixFiles, { file, path: `test/${file.name}` }]); + } + }} + /> + +
    +
    + {(submissionDescription.complementFixFiles ?? []).map((item, idx) => ( +
  • +
    + { + const fixFiles = getValues(`descriptions.${index}.complementFixFiles`); + setValue( + `descriptions.${index}.complementFixFiles`, + fixFiles.filter((_, fileIndex) => fileIndex !== idx) + ); + }} + /> +

    {item.file.name}

    +
    + +
    +

    {t("Submissions.filePath")}:

    + +
    +
  • + ))} +
    + + +
    + + {/* Test PR section */} + {!submissionDescription.testNotApplicable && ( + <> +

    {t("Submissions.addTestFiles")}:

    +

    {t("Submissions.addTestFilesExplanation")}

    + { + if (!a?.length) return; + for (const file of a) { + const testFiles = getValues(`descriptions.${index}.complementTestFiles`); + if (testFiles.some((f) => f.file.ipfsHash === file.ipfsHash)) continue; + setValue(`descriptions.${index}.complementTestFiles`, [...testFiles, { file, path: `test/${file.name}` }]); + } + }} + /> + + {/*

    {t("Submissions.filesAttached")}:

    */} +
    +
    + {(submissionDescription.complementTestFiles ?? []).map((item, idx) => ( +
  • +
    + { + const testFiles = getValues(`descriptions.${index}.complementTestFiles`); + setValue( + `descriptions.${index}.complementTestFiles`, + testFiles.filter((_, fileIndex) => fileIndex !== idx) + ); + }} + /> +

    {item.file.name}

    +
    + +
    +

    {t("Submissions.filePath")}:

    + +
    +
  • + ))} +
    +
    + + )} +
    +
    )} -
    - -
    ); }; @@ -502,6 +560,8 @@ export function SubmissionDescriptions() { )}
    + + {(isLoadingClaimedIssues || isLoadingGH) && } ); } diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts index 2e16fcd3b..18ff8e7c4 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts @@ -22,6 +22,7 @@ export const getCreateDescriptionSchema = (intl: TFunction) => if (type === "complement") return schema; return schema.required(intl("required")); }), + isFixApplicable: Yup.boolean(), // complement fields testNotApplicable: Yup.boolean(), diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/utils.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/utils.ts index 25198b105..853286e93 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/utils.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/utils.ts @@ -1,5 +1,6 @@ import { IPFS_PREFIX } from "constants/constants"; import { BASE_SERVICE_URL } from "settings"; +import { IHackerProfile } from "types"; import { ISubmissionData, ISubmissionsDescriptionsData } from "../../types"; export const SUBMISSION_DESCRIPTION_TEMPLATE = `**Description**\\ @@ -21,7 +22,8 @@ Describe how the vulnerability can be exploited. export const getAuditSubmissionTexts = ( submissionData: ISubmissionData, - descriptions: ISubmissionsDescriptionsData["descriptions"] + descriptions: ISubmissionsDescriptionsData["descriptions"], + hackerProfile: IHackerProfile | undefined ) => { const toEncrypt = `**Communication channel:** ${submissionData.contact?.communicationChannel} (${ submissionData.contact?.communicationChannelType @@ -49,7 +51,8 @@ ${description.complementTestFiles.map((file) => ` - ${file.path} (${IPFS_PREFIX const decrypted = `**Project Name:** ${submissionData.project?.projectName}\n **Project Id:** ${submissionData.project?.projectId}\n **Github username:** ${submissionData.contact?.githubUsername || "---"}\n -**Twitter username:** ${submissionData.contact?.twitterUsername || "---"} +**Twitter username:** ${submissionData.contact?.twitterUsername || "---"}\n +**HATS Profile:** ${hackerProfile ? `@${hackerProfile?.username}` : "---"} ${descriptions .filter((description) => !description.isEncrypted) @@ -85,11 +88,13 @@ ${description.complementTestFiles.map((file) => ` - ${file.path} (${IPFS_PREFIX export const getBountySubmissionTexts = ( submissionData: ISubmissionData, - descriptions: ISubmissionsDescriptionsData["descriptions"] + descriptions: ISubmissionsDescriptionsData["descriptions"], + hackerProfile: IHackerProfile | undefined ) => { const toEncrypt = `**Project Name:** ${submissionData.project?.projectName}\n **Project Id:** ${submissionData.project?.projectId}\n **Beneficiary:** ${submissionData.contact?.beneficiary}\n +**HATS Profile:** ${hackerProfile ? `@${hackerProfile?.username}` : "---"}\n **Communication channel:** ${submissionData.contact?.communicationChannel} (${submissionData.contact?.communicationChannelType}) ${descriptions @@ -112,12 +117,14 @@ ${description.description.trim()} export const getGithubIssueDescription = ( submissionData: ISubmissionData, - description: ISubmissionsDescriptionsData["descriptions"][0] + description: ISubmissionsDescriptionsData["descriptions"][0], + hackerProfile: IHackerProfile | undefined ) => { if (description.type === "new") { return `${submissionData.ref === "audit-wizard" ? "***Submitted via auditwizard.io***\n" : ""} **Github username:** ${submissionData.contact?.githubUsername ? `@${submissionData.contact?.githubUsername}` : "--"} **Twitter username:** ${submissionData.contact?.twitterUsername ? `${submissionData.contact?.twitterUsername}` : "--"} + **HATS Profile:** ${hackerProfile ? `@${hackerProfile?.username}` : "---"} **Submission hash (on-chain):** ${submissionData.submissionResult?.transactionHash} **Severity:** ${description.severity} diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/SubmissionFormPage.tsx b/packages/web/src/pages/Submissions/SubmissionFormPage/SubmissionFormPage.tsx index 770521e95..298c8a0f6 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/SubmissionFormPage.tsx +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/SubmissionFormPage.tsx @@ -5,6 +5,7 @@ import { LocalStorage } from "constants/constants"; import { LogClaimContract } from "contracts"; import { useVaults } from "hooks/subgraph/vaults/useVaults"; import useConfirm from "hooks/useConfirm"; +import { useProfileByAddress } from "pages/HackerProfile/hooks"; import { useUserHasCollectedSignature } from "pages/Honeypots/VaultDetailsPage/hooks"; import { HoneypotsRoutePaths } from "pages/Honeypots/router"; import { calcCid } from "pages/Submissions/SubmissionFormPage/encrypt"; @@ -14,7 +15,7 @@ import { useNavigate, useSearchParams } from "react-router-dom"; import { IS_PROD } from "settings"; import { getAppVersion } from "utils"; import { slugify } from "utils/slug.utils"; -import { useNetwork, useWaitForTransaction } from "wagmi"; +import { useAccount, useNetwork, useWaitForTransaction } from "wagmi"; import { SubmissionContactInfo, SubmissionDescriptions, @@ -41,6 +42,9 @@ export const SubmissionFormPage = () => { const navigate = useNavigate(); const [searchParams] = useSearchParams(); const { chain } = useNetwork(); + const { address } = useAccount(); + const { data: hackerProfile } = useProfileByAddress(address); + const [currentStep, setCurrentStep] = useState(); const [submissionData, setSubmissionData] = useState(); const [allFormDisabled, setAllFormDisabled] = useState(false); @@ -167,7 +171,7 @@ export const SubmissionFormPage = () => { }); try { - const res = await submitVulnerabilitySubmission(data, vault); + const res = await submitVulnerabilitySubmission(data, vault, hackerProfile); if (res.success) { setSubmissionData({ diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/store.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/store.ts index c977af6ef..33916ae44 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/store.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/store.ts @@ -39,6 +39,7 @@ export const SUBMISSION_INIT_DATA = { complementFixFiles: [], complementTestFiles: [], testNotApplicable: false, + isFixApplicable: false, }, ], } satisfies ISubmissionsDescriptionsData, diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/submissionsService.api.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/submissionsService.api.ts index 3ff2815fa..76359b163 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/submissionsService.api.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/submissionsService.api.ts @@ -1,4 +1,4 @@ -import { GithubIssue, IVault } from "@hats.finance/shared"; +import { GithubIssue, IHackerProfile, IVault } from "@hats.finance/shared"; import { axiosClient } from "config/axiosClient"; import { auditWizardVerifyService } from "constants/constants"; import { BASE_SERVICE_URL } from "settings"; @@ -14,7 +14,8 @@ import { IAuditWizardSubmissionData, ISubmissionData, ISubmitSubmissionRequest } */ export async function submitVulnerabilitySubmission( submissionData: ISubmissionData, - vault: IVault + vault: IVault, + hackerProfile: IHackerProfile | undefined ): Promise<{ success: boolean; auditCompetitionRepo?: string }> { if (!submissionData.project || !submissionData.submissionsDescriptions || !submissionData.submissionResult) { throw new Error(`Invalid params on 'submitVulnerabilitySubmission' function: ${submissionData}`); @@ -34,7 +35,7 @@ export async function submitVulnerabilitySubmission( ?.filter((desc) => !desc.isEncrypted && desc.type === "new") ?.map((description) => ({ issueTitle: description.title, - issueDescription: getGithubIssueDescription(submissionData, description), + issueDescription: getGithubIssueDescription(submissionData, description, hackerProfile), issueFiles: description.files?.map((file) => file.ipfsHash), })) : [], @@ -44,7 +45,7 @@ export async function submitVulnerabilitySubmission( ?.filter((desc) => !desc.isEncrypted && desc.type === "complement") ?.map((description) => ({ pullRequestTitle: `Complementary submission for #${description.complementGhIssueNumber}`, - pullRequestDescription: getGithubIssueDescription(submissionData, description), + pullRequestDescription: getGithubIssueDescription(submissionData, description, hackerProfile), pullRequestFiles: [...description.complementFixFiles, ...description.complementTestFiles].map((file) => ({ path: file.path, fileIpfsHash: file.file.ipfsHash, diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts index f2828703a..3e7f2b424 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts @@ -46,6 +46,7 @@ export interface ISubmissionsDescriptionsData { files: ISavedFile[]; sessionKey?: SessionKey; isEncrypted?: boolean; + isFixApplicable?: boolean; }[]; } From be3e0540f139fd15c3f6a7f6f7c301a01b511014 Mon Sep 17 00:00:00 2001 From: Carlos Fontes Date: Wed, 30 Oct 2024 14:25:14 +0000 Subject: [PATCH 05/25] chore: more advances --- .../pages/Submissions/SubmissionFormPage/SubmissionFormPage.tsx | 2 +- .../Submissions/SubmissionFormPage/submissionsService.api.ts | 1 + packages/web/src/pages/Submissions/SubmissionFormPage/types.ts | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/SubmissionFormPage.tsx b/packages/web/src/pages/Submissions/SubmissionFormPage/SubmissionFormPage.tsx index 298c8a0f6..0d9c3b998 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/SubmissionFormPage.tsx +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/SubmissionFormPage.tsx @@ -190,7 +190,7 @@ export const SubmissionFormPage = () => { }); } }, - [vault] + [vault, hackerProfile] ); const submitSubmission = useCallback(async () => { diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/submissionsService.api.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/submissionsService.api.ts index 76359b163..a8c3a89d5 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/submissionsService.api.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/submissionsService.api.ts @@ -37,6 +37,7 @@ export async function submitVulnerabilitySubmission( issueTitle: description.title, issueDescription: getGithubIssueDescription(submissionData, description, hackerProfile), issueFiles: description.files?.map((file) => file.ipfsHash), + isFixApplicable: description.isFixApplicable, })) : [], createPRsRequests: diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts index 3e7f2b424..31b04c22f 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts @@ -92,6 +92,7 @@ export interface ISubmitSubmissionRequest { issueTitle: string; issueDescription: string; issueFiles: string[]; + isFixApplicable?: boolean; }[]; createPRsRequests: { pullRequestTitle: string; From 5e84dee27d18bb3d5747252a87dbdeb87d9d55f0 Mon Sep 17 00:00:00 2001 From: Carlos Fontes Date: Wed, 30 Oct 2024 17:44:12 +0000 Subject: [PATCH 06/25] chore: different test/prod leaderboard params --- .../components/SplitPointsActions.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/components/SplitPointsActions.tsx b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/components/SplitPointsActions.tsx index b06135ed6..654eb7360 100644 --- a/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/components/SplitPointsActions.tsx +++ b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/components/SplitPointsActions.tsx @@ -40,8 +40,8 @@ export const SplitPointsActions = ({ vault, submission }: SplitPointsActionsProp const connectedChain = chain ? appChains[chain.id] : null; const isTestnet = !IS_PROD && connectedChain?.chain.testnet; - const TOP_LEADERBOARD_PERCENTAGE = isTestnet ? 1 : 0.2; - const TOP_LEADERBOARD_MIN_REWARDS = isTestnet ? 0 : 5000; + const TOP_LEADERBOARD_PERCENTAGE = 0.2; + const TOP_LEADERBOARD_MIN_REWARDS = 5000; const isConnected = !!address; const isProfileCreated = !!profile; @@ -56,9 +56,9 @@ export const SplitPointsActions = ({ vault, submission }: SplitPointsActionsProp const leaderboardInBoundaries = leaderboard .slice(0, Math.floor(leaderboard.length * TOP_LEADERBOARD_PERCENTAGE)) .filter((entry) => entry.totalAmount.usd >= TOP_LEADERBOARD_MIN_REWARDS); - const isInLeadearboardBoundaries = leaderboardInBoundaries.find( - (entry) => entry.address.toLowerCase() === address?.toLowerCase() - ); + const isInLeadearboardBoundaries = isTestnet + ? true + : leaderboardInBoundaries.find((entry) => entry.address.toLowerCase() === address?.toLowerCase()); const claimInfo = claimedIssues?.find((issue) => +issue.issueNumber === +submission.number); const claimedByInfo = getClaimedBy(claimInfo); From 83f4bd53bb689e407c0c0c67b0c5cdaf13bffaaf Mon Sep 17 00:00:00 2001 From: Carlos Fontes Date: Wed, 30 Oct 2024 17:44:39 +0000 Subject: [PATCH 07/25] chore: added `needs-test` and `needs-fix` to model --- .../FormSteps/SubmissionDescriptions/formSchema.ts | 14 ++++++++++---- .../SubmissionFormPage/SubmissionFormPage.tsx | 4 +++- .../pages/Submissions/SubmissionFormPage/store.ts | 2 ++ .../pages/Submissions/SubmissionFormPage/types.ts | 2 ++ 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts index 18ff8e7c4..96b834b18 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts @@ -25,6 +25,8 @@ export const getCreateDescriptionSchema = (intl: TFunction) => isFixApplicable: Yup.boolean(), // complement fields + needsFix: Yup.boolean(), + needsTest: Yup.boolean(), testNotApplicable: Yup.boolean(), complementGhIssueNumber: Yup.string().when("type", (type: "new" | "complement", schema: any) => { if (type === "new") return schema; @@ -46,9 +48,12 @@ export const getCreateDescriptionSchema = (intl: TFunction) => }), }) ) - .when("type", (type: "new" | "complement", schema: any) => { - if (type === "new") return schema; - return schema.required(intl("required")).min(1, intl("required")); + .test("min", intl("required"), (val, ctx: any) => { + const type = ctx.from[0].value.type; + const needsFix = ctx.from[0].value.needsFix; + if (type === "new") return true; + if (!needsFix) return true; + return (val?.length ?? 0) > 0 ?? false; }), complementTestFiles: Yup.array() .of( @@ -65,8 +70,9 @@ export const getCreateDescriptionSchema = (intl: TFunction) => .test("min", intl("required"), (val, ctx: any) => { const type = ctx.from[0].value.type; const testNotApplicable = ctx.from[0].value.testNotApplicable; + const needsTest = ctx.from[0].value.needsTest; if (type === "new") return true; - if (testNotApplicable) return true; + if (testNotApplicable || !needsTest) return true; return (val?.length ?? 0) > 0 ?? false; }), }) diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/SubmissionFormPage.tsx b/packages/web/src/pages/Submissions/SubmissionFormPage/SubmissionFormPage.tsx index 0d9c3b998..42914fe55 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/SubmissionFormPage.tsx +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/SubmissionFormPage.tsx @@ -313,6 +313,8 @@ export const SubmissionFormPage = () => { isEncrypted: desc.isEncrypted, files: desc.files || [], testNotApplicable: false, + needsFix: false, + needsTest: false, })), }, submissionResult: undefined, @@ -397,7 +399,7 @@ export const SubmissionFormPage = () => {
    - {isLoadingCollectedSignature && } + {requireMessageSignature && isLoadingCollectedSignature && } ); }; diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/store.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/store.ts index 33916ae44..287dd1bc3 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/store.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/store.ts @@ -40,6 +40,8 @@ export const SUBMISSION_INIT_DATA = { complementTestFiles: [], testNotApplicable: false, isFixApplicable: false, + needsFix: false, + needsTest: false, }, ], } satisfies ISubmissionsDescriptionsData, diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts index 31b04c22f..97346650c 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts @@ -38,6 +38,8 @@ export interface ISubmissionsDescriptionsData { complementFixFiles: { file: ISavedFile; path: string }[]; complementGhIssueNumber?: string; complementGhIssue?: GithubIssue; + needsFix: boolean; + needsTest: boolean; // new fields title: string; From c8b93cff66d54dd8f1779e2672c6b318b484a994 Mon Sep 17 00:00:00 2001 From: Carlos Fontes Date: Wed, 30 Oct 2024 17:44:58 +0000 Subject: [PATCH 08/25] chore: showing complementary labels in submission form --- .../SubmissionDescriptions.tsx | 151 ++++++++++-------- .../SubmissionDescriptions/styles.ts | 13 ++ .../FormSteps/SubmissionDescriptions/utils.ts | 10 +- 3 files changed, 104 insertions(+), 70 deletions(-) diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx index ad728f62b..4d7cb7594 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx @@ -13,6 +13,7 @@ import { FormSelectInputOption, FormSupportFilesInput, Loading, + Pill, WithTooltip, } from "components"; import download from "downloadjs"; @@ -48,7 +49,6 @@ export function SubmissionDescriptions() { const isPrivateAudit = vault?.description?.["project-metadata"].isPrivateAudit; const { data: claimedIssues, isLoading: isLoadingClaimedIssues } = useClaimedIssuesByVaultAndClaimedBy(vault, address); - console.log(claimedIssues); const [vaultGithubIssuesOpts, setVaultGithubIssuesOpts] = useState(); const [vaultGithubIssues, setVaultGithubIssues] = useState(undefined); @@ -71,7 +71,6 @@ export function SubmissionDescriptions() { append: appendSubmissionDescription, remove: removeSubmissionDescription, } = useFieldArray({ control, name: `descriptions` }); - const watchDescriptions = useWatch({ control, name: `descriptions` }); const controlledDescriptions = fields.map((field, index) => { return { @@ -311,6 +310,8 @@ export function SubmissionDescriptions() { }; const getComplementIssueForm = (submissionDescription: (typeof controlledDescriptions)[number], index: number) => { + const { complementGhIssue, needsFix, needsTest } = submissionDescription; + return ( <>

    {t("Submissions.selectIssueToComplement")}

    @@ -345,76 +346,90 @@ export function SubmissionDescriptions() { onChange={(a) => { field.onChange(a); const ghIssue = vaultGithubIssues?.find((gh) => gh.number === Number(a as string)); - if (ghIssue) setValue(`descriptions.${index}.complementGhIssue`, ghIssue); + if (ghIssue) { + setValue(`descriptions.${index}.complementGhIssue`, ghIssue); + setValue(`descriptions.${index}.needsFix`, ghIssue.bonusPointsLabels.needsFix); + setValue(`descriptions.${index}.needsTest`, ghIssue.bonusPointsLabels.needsTest); + } }} /> )} /> + {/* labels */} +
    +
    + {complementGhIssue?.bonusPointsLabels.needsFix && } + {complementGhIssue?.bonusPointsLabels.needsTest && } +
    +
    + {/* Fix PR section */} - <> -

    {t("Submissions.addFixFiles")}:

    -

    {t("Submissions.addFixFilesExplanation")}

    - { - if (!a?.length) return; - for (const file of a) { - const fixFiles = getValues(`descriptions.${index}.complementFixFiles`); - if (fixFiles.some((f) => f.file.ipfsHash === file.ipfsHash)) continue; - setValue(`descriptions.${index}.complementFixFiles`, [...fixFiles, { file, path: `test/${file.name}` }]); + {needsFix && ( + <> +

    {t("Submissions.addFixFiles")}:

    +

    {t("Submissions.addFixFilesExplanation")}

    + + onChange={(a) => { + if (!a?.length) return; + for (const file of a) { + const fixFiles = getValues(`descriptions.${index}.complementFixFiles`); + if (fixFiles.some((f) => f.file.ipfsHash === file.ipfsHash)) continue; + setValue(`descriptions.${index}.complementFixFiles`, [...fixFiles, { file, path: `test/${file.name}` }]); + } + }} + /> + +
    +
    + {(submissionDescription.complementFixFiles ?? []).map((item, idx) => ( +
  • +
    + { + const fixFiles = getValues(`descriptions.${index}.complementFixFiles`); + setValue( + `descriptions.${index}.complementFixFiles`, + fixFiles.filter((_, fileIndex) => fileIndex !== idx) + ); + }} + /> +

    {item.file.name}

    +
    -
    -
    - {(submissionDescription.complementFixFiles ?? []).map((item, idx) => ( -
  • -
    - { - const fixFiles = getValues(`descriptions.${index}.complementFixFiles`); - setValue( - `descriptions.${index}.complementFixFiles`, - fixFiles.filter((_, fileIndex) => fileIndex !== idx) - ); - }} - /> -

    {item.file.name}

    -
    - -
    -

    {t("Submissions.filePath")}:

    - -
    -
  • - ))} +
    +

    {t("Submissions.filePath")}:

    + +
    + + ))} +
    -
    - + + )}
    {/* Test PR section */} - {!submissionDescription.testNotApplicable && ( + {!submissionDescription.testNotApplicable && needsTest && ( <>

    {t("Submissions.addTestFiles")}:

    {t("Submissions.addTestFilesExplanation")}

    @@ -475,14 +490,16 @@ export function SubmissionDescriptions() {
    )} -
    - -
    + {needsTest && needsFix && ( +
    + +
    + )} )} diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/styles.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/styles.ts index c0ef49b82..98bc38c9c 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/styles.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/styles.ts @@ -46,6 +46,19 @@ export const StyledSubmissionDescription = styled.div<{ isEncrypted: boolean }>( } } + .gh-labels { + display: flex; + flex-direction: column; + gap: ${getSpacing(1)}; + margin-bottom: ${getSpacing(2.5)}; + margin-top: ${getSpacing(-1.5)}; + + .labels { + display: flex; + gap: ${getSpacing(1)}; + } + } + .options { display: flex; gap: ${getSpacing(3)}; diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/utils.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/utils.ts index 853286e93..fb97f35da 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/utils.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/utils.ts @@ -52,7 +52,8 @@ ${description.complementTestFiles.map((file) => ` - ${file.path} (${IPFS_PREFIX **Project Id:** ${submissionData.project?.projectId}\n **Github username:** ${submissionData.contact?.githubUsername || "---"}\n **Twitter username:** ${submissionData.contact?.twitterUsername || "---"}\n -**HATS Profile:** ${hackerProfile ? `@${hackerProfile?.username}` : "---"} +**HATS Profile:** ${hackerProfile ? `${hackerProfile?.username}` : "---"}\n +**Beneficiary:** ${submissionData.contact?.beneficiary} ${descriptions .filter((description) => !description.isEncrypted) @@ -94,7 +95,7 @@ export const getBountySubmissionTexts = ( const toEncrypt = `**Project Name:** ${submissionData.project?.projectName}\n **Project Id:** ${submissionData.project?.projectId}\n **Beneficiary:** ${submissionData.contact?.beneficiary}\n -**HATS Profile:** ${hackerProfile ? `@${hackerProfile?.username}` : "---"}\n +**HATS Profile:** ${hackerProfile ? `${hackerProfile?.username}` : "---"}\n **Communication channel:** ${submissionData.contact?.communicationChannel} (${submissionData.contact?.communicationChannelType}) ${descriptions @@ -124,7 +125,8 @@ export const getGithubIssueDescription = ( return `${submissionData.ref === "audit-wizard" ? "***Submitted via auditwizard.io***\n" : ""} **Github username:** ${submissionData.contact?.githubUsername ? `@${submissionData.contact?.githubUsername}` : "--"} **Twitter username:** ${submissionData.contact?.twitterUsername ? `${submissionData.contact?.twitterUsername}` : "--"} - **HATS Profile:** ${hackerProfile ? `@${hackerProfile?.username}` : "---"} + **HATS Profile:** ${hackerProfile ? `${hackerProfile?.username}` : "---"} + **Beneficiary:** ${submissionData.contact?.beneficiary} **Submission hash (on-chain):** ${submissionData.submissionResult?.transactionHash} **Severity:** ${description.severity} @@ -143,6 +145,8 @@ export const getGithubIssueDescription = ( return `**Github username:** ${submissionData.contact?.githubUsername ? `@${submissionData.contact?.githubUsername}` : "--"} **Twitter username:** ${submissionData.contact?.twitterUsername ? `${submissionData.contact?.twitterUsername}` : "--"} +**HATS Profile:** ${hackerProfile ? `${hackerProfile?.username}` : "---"} +**Beneficiary:** ${submissionData.contact?.beneficiary} **Submission hash (on-chain):** ${submissionData.submissionResult?.transactionHash} **Description:** From 76fad4ea6c37eb558d58721e47336741f941b8bc Mon Sep 17 00:00:00 2001 From: Carlos Fontes Date: Wed, 30 Oct 2024 17:56:59 +0000 Subject: [PATCH 09/25] fix: showing only issues that needs something in the select input --- .../FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx index 4d7cb7594..ccce040f6 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx @@ -142,6 +142,7 @@ export function SubmissionDescriptions() { const ghIssues = await getGithubIssuesFromVault(vault); const ghIssuesOpts = ghIssues .filter((ghIssue) => claimedIssues?.some((ci) => +ci.issueNumber === +ghIssue.number)) + .filter((ghIssue) => ghIssue.bonusPointsLabels.needsFix || ghIssue.bonusPointsLabels.needsTest) .map((ghIssue) => ({ label: `[#${ghIssue.number}] ${ghIssue.title}`, value: `${ghIssue.number}`, From ea4838024b5e63dd5e0e8f171eb671b6810e5465 Mon Sep 17 00:00:00 2001 From: Carlos Fontes Date: Wed, 30 Oct 2024 19:08:27 +0000 Subject: [PATCH 10/25] feat: implemented path autocomplete --- .../SubmissionDescriptions.tsx | 160 +++++++++++++----- .../SubmissionDescriptions/formSchema.ts | 4 +- .../SubmissionDescriptions/styles.ts | 70 +++++--- .../Submissions/SubmissionFormPage/types.ts | 4 +- packages/web/src/utils/github.utils.ts | 14 ++ 5 files changed, 180 insertions(+), 72 deletions(-) diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx index ccce040f6..3a6d58900 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx @@ -21,11 +21,13 @@ import { getCustomIsDirty, useEnhancedForm } from "hooks/form"; import { getGithubIssuesFromVault } from "pages/CommitteeTools/SubmissionsTool/submissionsService.api"; import { useProfileByAddress } from "pages/HackerProfile/hooks"; import { useClaimedIssuesByVaultAndClaimedBy } from "pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/hooks"; +import { getVaultRepoName } from "pages/Honeypots/VaultDetailsPage/savedSubmissionsService"; import { HoneypotsRoutePaths } from "pages/Honeypots/router"; import { useContext, useEffect, useState } from "react"; import { Controller, useFieldArray, useWatch } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; +import { searchFileInHatsRepo } from "utils/github.utils"; import { slugify } from "utils/slug.utils"; import { useAccount } from "wagmi"; import { encryptWithHatsKey, encryptWithKeys } from "../../encrypt"; @@ -382,12 +384,33 @@ export function SubmissionDescriptions() { ? (errors.descriptions?.[index]?.complementFixFiles as never) : undefined } - onChange={(a) => { + onChange={async (a) => { if (!a?.length) return; for (const file of a) { const fixFiles = getValues(`descriptions.${index}.complementFixFiles`); if (fixFiles.some((f) => f.file.ipfsHash === file.ipfsHash)) continue; - setValue(`descriptions.${index}.complementFixFiles`, [...fixFiles, { file, path: `test/${file.name}` }]); + const newFile = { file, path: "", pathOpts: [] }; + + const repoName = await getVaultRepoName(vault?.id); + if (!repoName) { + setValue(`descriptions.${index}.complementFixFiles`, [...fixFiles, newFile]); + return; + } + + const pathOptions = await searchFileInHatsRepo(repoName, file.name); + if (pathOptions.length === 0) { + setValue(`descriptions.${index}.complementFixFiles`, [...fixFiles, newFile]); + } else if (pathOptions.length === 1) { + setValue(`descriptions.${index}.complementFixFiles`, [ + ...fixFiles, + { ...newFile, path: pathOptions[0], pathOpts: [] }, + ]); + } else { + setValue(`descriptions.${index}.complementFixFiles`, [ + ...fixFiles, + { ...newFile, path: "", pathOpts: pathOptions }, + ]); + } } }} /> @@ -396,29 +419,41 @@ export function SubmissionDescriptions() {
    {(submissionDescription.complementFixFiles ?? []).map((item, idx) => (
  • -
    - { - const fixFiles = getValues(`descriptions.${index}.complementFixFiles`); - setValue( - `descriptions.${index}.complementFixFiles`, - fixFiles.filter((_, fileIndex) => fileIndex !== idx) - ); - }} - /> -

    {item.file.name}

    +
    +
    + { + const fixFiles = getValues(`descriptions.${index}.complementFixFiles`); + setValue( + `descriptions.${index}.complementFixFiles`, + fixFiles.filter((_, fileIndex) => fileIndex !== idx) + ); + }} + /> +

    {item.file?.name}

    +
    + +
    +

    {t("Submissions.filePath")}:

    +
    + +
    +
    -
    -

    {t("Submissions.filePath")}:

    - +
    + {item.pathOpts?.map((opt) => ( +
    setValue(`descriptions.${index}.complementFixFiles.${idx}.path`, opt)}> + +
    + ))}
  • ))} @@ -446,12 +481,33 @@ export function SubmissionDescriptions() { ? (errors.descriptions?.[index]?.complementTestFiles as never) : undefined } - onChange={(a) => { + onChange={async (a) => { if (!a?.length) return; for (const file of a) { const testFiles = getValues(`descriptions.${index}.complementTestFiles`); if (testFiles.some((f) => f.file.ipfsHash === file.ipfsHash)) continue; - setValue(`descriptions.${index}.complementTestFiles`, [...testFiles, { file, path: `test/${file.name}` }]); + const newFile = { file, path: "", pathOpts: [] }; + + const repoName = await getVaultRepoName(vault?.id); + if (!repoName) { + setValue(`descriptions.${index}.complementTestFiles`, [...testFiles, newFile]); + return; + } + + const pathOptions = await searchFileInHatsRepo(repoName, file.name); + if (pathOptions.length === 0) { + setValue(`descriptions.${index}.complementTestFiles`, [...testFiles, newFile]); + } else if (pathOptions.length === 1) { + setValue(`descriptions.${index}.complementTestFiles`, [ + ...testFiles, + { ...newFile, path: pathOptions[0], pathOpts: [] }, + ]); + } else { + setValue(`descriptions.${index}.complementTestFiles`, [ + ...testFiles, + { ...newFile, path: "", pathOpts: pathOptions }, + ]); + } } }} /> @@ -461,29 +517,41 @@ export function SubmissionDescriptions() {
    {(submissionDescription.complementTestFiles ?? []).map((item, idx) => (
  • -
    - { - const testFiles = getValues(`descriptions.${index}.complementTestFiles`); - setValue( - `descriptions.${index}.complementTestFiles`, - testFiles.filter((_, fileIndex) => fileIndex !== idx) - ); - }} - /> -

    {item.file.name}

    +
    +
    + { + const testFiles = getValues(`descriptions.${index}.complementTestFiles`); + setValue( + `descriptions.${index}.complementTestFiles`, + testFiles.filter((_, fileIndex) => fileIndex !== idx) + ); + }} + /> +

    {item.file?.name}

    +
    + +
    +

    {t("Submissions.filePath")}:

    +
    + +
    +
    -
    -

    {t("Submissions.filePath")}:

    - +
    + {item.pathOpts?.map((opt) => ( +
    setValue(`descriptions.${index}.complementTestFiles.${idx}.path`, opt)}> + +
    + ))}
  • ))} diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts index 96b834b18..8bc4861ea 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts @@ -43,7 +43,7 @@ export const getCreateDescriptionSchema = (intl: TFunction) => path: Yup.string() .required(intl("required")) .test("pathEndsWithFileNameError", intl("pathEndsWithFileNameError"), (val, ctx: any) => { - const fileName = ctx.from[0].value.file.name; + const fileName = ctx.from[0].value.file?.name; return val?.endsWith(fileName) ?? false; }), }) @@ -62,7 +62,7 @@ export const getCreateDescriptionSchema = (intl: TFunction) => path: Yup.string() .required(intl("required")) .test("pathEndsWithFileNameError", intl("pathEndsWithFileNameError"), (val, ctx: any) => { - const fileName = ctx.from[0].value.file.name; + const fileName = ctx.from[0].value.file?.name; return val?.endsWith(fileName) ?? false; }), }) diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/styles.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/styles.ts index 98bc38c9c..a6020df06 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/styles.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/styles.ts @@ -132,39 +132,65 @@ export const StyledSubmissionDescription = styled.div<{ isEncrypted: boolean }>( li { list-style: none; display: flex; - gap: ${getSpacing(5)}; width: 100%; align-items: flex-start; + flex-direction: column; - .file { + .file-container { display: flex; - align-items: center; - gap: ${getSpacing(1)}; - border: 1px solid var(--primary); - border-radius: 50px; - padding: ${getSpacing(0.5)} ${getSpacing(1)}; - font-size: var(--xxsmall); - margin-top: ${getSpacing(2)}; - - .remove-icon { - cursor: pointer; - transition: 0.1s; + gap: ${getSpacing(5)}; + width: 100%; + align-items: flex-start; - &:hover { - color: var(--error-red); + .file { + display: flex; + align-items: center; + gap: ${getSpacing(1)}; + border: 1px solid var(--primary); + border-radius: 50px; + padding: ${getSpacing(0.5)} ${getSpacing(1)}; + font-size: var(--xxsmall); + margin-top: ${getSpacing(2)}; + + .remove-icon { + cursor: pointer; + transition: 0.1s; + + &:hover { + color: var(--error-red); + } + } + } + + .file-path { + display: flex; + align-items: flex-start; + gap: ${getSpacing(2)}; + width: 100%; + + p { + white-space: nowrap; + margin-top: ${getSpacing(2.5)}; + } + + .file-path-input { + width: 100%; } } } - .file-path { + .file-opts { display: flex; - align-items: flex-start; - gap: ${getSpacing(2)}; - width: 100%; + gap: ${getSpacing(0.5)}; + flex-wrap: wrap; - p { - white-space: nowrap; - margin-top: ${getSpacing(2.5)}; + div { + cursor: pointer; + transition: 0.2s; + + &:hover { + opacity: 0.8; + } } } } diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts index 97346650c..28d7a7bd5 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts @@ -34,8 +34,8 @@ export interface ISubmissionsDescriptionsData { // complement fields testNotApplicable: boolean; - complementTestFiles: { file: ISavedFile; path: string }[]; - complementFixFiles: { file: ISavedFile; path: string }[]; + complementTestFiles: { file: ISavedFile; path: string; pathOpts: string[] }[]; + complementFixFiles: { file: ISavedFile; path: string; pathOpts: string[] }[]; complementGhIssueNumber?: string; complementGhIssue?: GithubIssue; needsFix: boolean; diff --git a/packages/web/src/utils/github.utils.ts b/packages/web/src/utils/github.utils.ts index 39c77d4d7..fdd171da9 100644 --- a/packages/web/src/utils/github.utils.ts +++ b/packages/web/src/utils/github.utils.ts @@ -1,4 +1,18 @@ +import { axiosClient } from "config/axiosClient"; +import { BASE_SERVICE_URL } from "settings"; + export const isGithubUsernameValid = async (username: string) => { const response = await fetch(`https://api.github.com/users/${username}`); return response.status === 200; }; + +export const searchFileInHatsRepo = async (repoName: string, fileName: string): Promise => { + try { + const res = await axiosClient.get(`${BASE_SERVICE_URL}/utils/search-file-in-repo?repoName=${repoName}&fileName=${fileName}`); + + return res.data.files ?? []; + } catch (err) { + console.error(err); + throw new Error(`Error getting prices: ${err}`); + } +}; From 73a9ca5448f4310a5f7511d6706011a2c152d1de Mon Sep 17 00:00:00 2001 From: Carlos Fontes Date: Tue, 5 Nov 2024 11:19:18 +0000 Subject: [PATCH 11/25] refactor: changed `fixIsNotApplicable` to `testIsNotApplicable` --- packages/web/src/languages/en.json | 2 +- .../FormSteps/SubmissionDescriptions/formSchema.ts | 2 +- .../web/src/pages/Submissions/SubmissionFormPage/store.ts | 2 +- .../Submissions/SubmissionFormPage/submissionsService.api.ts | 2 +- .../web/src/pages/Submissions/SubmissionFormPage/types.ts | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/web/src/languages/en.json b/packages/web/src/languages/en.json index 199daaf65..3224f0483 100644 --- a/packages/web/src/languages/en.json +++ b/packages/web/src/languages/en.json @@ -736,7 +736,7 @@ "onlyShowLabeledIssues": "Show only labeled issues", "ghIssue": "GH issue", "newSubmission": "New issue", - "complementSubmission": "Add Fix & Test to existing issue", + "complementSubmission": "Add Fix & Test to an existing issue", "pathEndsWithFileNameError": "Invalid path (path needs to end with file name)", "youNeedToConnectYourWallet": "You need to connect your wallet.", "youAreNotInTopLeaderboardPercentage": "This action is reserved for participants in the top of HATS leaderboard. Improve your ranking to unlock or connect with your profile.", diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts index 8bc4861ea..438c4e69d 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts @@ -22,7 +22,7 @@ export const getCreateDescriptionSchema = (intl: TFunction) => if (type === "complement") return schema; return schema.required(intl("required")); }), - isFixApplicable: Yup.boolean(), + isTestApplicable: Yup.boolean(), // complement fields needsFix: Yup.boolean(), diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/store.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/store.ts index 287dd1bc3..033cdd31b 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/store.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/store.ts @@ -39,7 +39,7 @@ export const SUBMISSION_INIT_DATA = { complementFixFiles: [], complementTestFiles: [], testNotApplicable: false, - isFixApplicable: false, + isTestApplicable: false, needsFix: false, needsTest: false, }, diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/submissionsService.api.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/submissionsService.api.ts index a8c3a89d5..deb5a6d5d 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/submissionsService.api.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/submissionsService.api.ts @@ -37,7 +37,7 @@ export async function submitVulnerabilitySubmission( issueTitle: description.title, issueDescription: getGithubIssueDescription(submissionData, description, hackerProfile), issueFiles: description.files?.map((file) => file.ipfsHash), - isFixApplicable: description.isFixApplicable, + isTestApplicable: description.isTestApplicable, })) : [], createPRsRequests: diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts index 28d7a7bd5..9b05f8972 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts @@ -48,7 +48,7 @@ export interface ISubmissionsDescriptionsData { files: ISavedFile[]; sessionKey?: SessionKey; isEncrypted?: boolean; - isFixApplicable?: boolean; + isTestApplicable?: boolean; }[]; } @@ -94,7 +94,7 @@ export interface ISubmitSubmissionRequest { issueTitle: string; issueDescription: string; issueFiles: string[]; - isFixApplicable?: boolean; + isTestApplicable?: boolean; }[]; createPRsRequests: { pullRequestTitle: string; From c6d7c9cd40d36b07e48549dcb040dfb1d72996b8 Mon Sep 17 00:00:00 2001 From: Carlos Fontes Date: Tue, 5 Nov 2024 11:19:28 +0000 Subject: [PATCH 12/25] cchore: improvement --- .../SubmissionDescriptions.tsx | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx index 3a6d58900..3d8bbc929 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx @@ -155,7 +155,12 @@ export function SubmissionDescriptions() { setIsLoadingGH(false); }; loadGhIssues(); - }, [vault, vaultGithubIssues, isLoadingGH, someComplementSubmission, claimedIssues]); + }, [vault, vaultGithubIssues, isLoadingGH, someComplementSubmission, claimedIssues, address]); + + useEffect(() => { + setVaultGithubIssuesOpts(undefined); + setVaultGithubIssues(undefined); + }, [address]); const handleSaveAndDownloadDescription = async (formData: ISubmissionsDescriptionsData) => { if (!vault) return; @@ -294,17 +299,17 @@ export function SubmissionDescriptions() {

    Does this issue needs a fix?

    -
    setValue(`descriptions.${index}.isFixApplicable`, true)}> -
    +
    setValue(`descriptions.${index}.isTestApplicable`, true)}> +
    -

    Fix is applicable

    +

    PoC is applicable

    -
    setValue(`descriptions.${index}.isFixApplicable`, false)}> -
    +
    setValue(`descriptions.${index}.isTestApplicable`, false)}> +
    -

    Fix is not applicable

    +

    PoC is not applicable

    From 2b7986726ca12ac5e5a9b310d97b10707a1e75af Mon Sep 17 00:00:00 2001 From: Carlos Fontes Date: Tue, 5 Nov 2024 11:25:54 +0000 Subject: [PATCH 13/25] chore: now PoC is applicable option begins undefined --- packages/web/src/languages/en.json | 2 ++ .../SubmissionDescriptions/SubmissionDescriptions.tsx | 6 +++--- .../FormSteps/SubmissionDescriptions/formSchema.ts | 2 +- .../FormSteps/SubmissionDescriptions/styles.ts | 4 ++++ .../web/src/pages/Submissions/SubmissionFormPage/store.ts | 2 +- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/web/src/languages/en.json b/packages/web/src/languages/en.json index 3224f0483..48110c1f5 100644 --- a/packages/web/src/languages/en.json +++ b/packages/web/src/languages/en.json @@ -1358,6 +1358,8 @@ "filePathPlaceholder": "src/path/to/file.sol", "testNotApplicable": "Test not applicable", "youNeedUploadAtLeastOneFile": "You need to upload at least one file", + "pocIsApplicable": "PoC is applicable", + "pocIsNotApplicable": "PoC is not applicable", "terms": { "bugBounty": { "submissionSection": "

    Your submission will be sent to the committee who will process your submission.

    The committee of the vault will respond within 24 hours to acknowledge the receipt of submission via the communication channel you provided.

    ", diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx index 3d8bbc929..7c59aac3f 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx @@ -298,18 +298,18 @@ export function SubmissionDescriptions() { )} */}

    Does this issue needs a fix?

    -
    +
    setValue(`descriptions.${index}.isTestApplicable`, true)}>
    -

    PoC is applicable

    +

    {t("Submissions.pocIsApplicable")}

    setValue(`descriptions.${index}.isTestApplicable`, false)}>
    -

    PoC is not applicable

    +

    {t("Submissions.pocIsNotApplicable")}

    diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts index 438c4e69d..ef66054e8 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts @@ -22,7 +22,7 @@ export const getCreateDescriptionSchema = (intl: TFunction) => if (type === "complement") return schema; return schema.required(intl("required")); }), - isTestApplicable: Yup.boolean(), + isTestApplicable: Yup.boolean().required(intl("required")), // complement fields needsFix: Yup.boolean(), diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/styles.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/styles.ts index a6020df06..95c8834ef 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/styles.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/styles.ts @@ -201,5 +201,9 @@ export const StyledSubmissionDescription = styled.div<{ isEncrypted: boolean }>( display: flex; justify-content: flex-end; } + + .error-text { + color: var(--error-red); + } ` ); diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/store.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/store.ts index 033cdd31b..6a250ca92 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/store.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/store.ts @@ -39,7 +39,7 @@ export const SUBMISSION_INIT_DATA = { complementFixFiles: [], complementTestFiles: [], testNotApplicable: false, - isTestApplicable: false, + isTestApplicable: undefined, needsFix: false, needsTest: false, }, From a0acae8159d58b9994e463a7816bbd3a412309db Mon Sep 17 00:00:00 2001 From: Carlos Fontes Date: Tue, 5 Nov 2024 11:31:47 +0000 Subject: [PATCH 14/25] fix: checking if issue is expired in order to show it as option --- .../SubmissionDescriptions/SubmissionDescriptions.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx index 7c59aac3f..27d374f28 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx @@ -18,6 +18,7 @@ import { } from "components"; import download from "downloadjs"; import { getCustomIsDirty, useEnhancedForm } from "hooks/form"; +import moment from "moment"; import { getGithubIssuesFromVault } from "pages/CommitteeTools/SubmissionsTool/submissionsService.api"; import { useProfileByAddress } from "pages/HackerProfile/hooks"; import { useClaimedIssuesByVaultAndClaimedBy } from "pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/hooks"; @@ -51,6 +52,7 @@ export function SubmissionDescriptions() { const isPrivateAudit = vault?.description?.["project-metadata"].isPrivateAudit; const { data: claimedIssues, isLoading: isLoadingClaimedIssues } = useClaimedIssuesByVaultAndClaimedBy(vault, address); + console.log(claimedIssues); const [vaultGithubIssuesOpts, setVaultGithubIssuesOpts] = useState(); const [vaultGithubIssues, setVaultGithubIssues] = useState(undefined); @@ -143,7 +145,9 @@ export function SubmissionDescriptions() { setIsLoadingGH(true); const ghIssues = await getGithubIssuesFromVault(vault); const ghIssuesOpts = ghIssues - .filter((ghIssue) => claimedIssues?.some((ci) => +ci.issueNumber === +ghIssue.number)) + .filter((ghIssue) => + claimedIssues?.some((ci) => +ci.issueNumber === +ghIssue.number && !moment(ci.expiresAt).isBefore(moment())) + ) .filter((ghIssue) => ghIssue.bonusPointsLabels.needsFix || ghIssue.bonusPointsLabels.needsTest) .map((ghIssue) => ({ label: `[#${ghIssue.number}] ${ghIssue.title}`, From 5ed3355c6ca38c387f3e28c5384aafbd3a25e0a8 Mon Sep 17 00:00:00 2001 From: Carlos Fontes Date: Tue, 5 Nov 2024 12:39:37 +0000 Subject: [PATCH 15/25] chore: added feature banner --- packages/web/src/languages/en.json | 3 ++ .../VaultSubmissionsSection.tsx | 25 ++++++++++++ .../VaultSubmissionsSection/styles.ts | 40 +++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/packages/web/src/languages/en.json b/packages/web/src/languages/en.json index 48110c1f5..bdb6a2000 100644 --- a/packages/web/src/languages/en.json +++ b/packages/web/src/languages/en.json @@ -744,6 +744,9 @@ "claimIssueDescription": "Are you sure you want to claim this issue?", "claimedByYou": "Claimed by you", "issueAlreadyClaimed": "This issue has already been claimed.", + "bonusPointsTitle": "New: Boost Your Rewards, Claim and Submit Fixes and Tests!", + "bonusPointsDescription": "As a top performer, you now have the exclusive opportunity to claim and submit fixes and tests for identified issues.\n
    • Increase your potential earnings by up to 15% per issue
    • Demonstrate your problem-solving skills
    • Contribute directly to improving security
    \n
    • Ensure you're in the top 20% of the leaderboard and won 5k of reward
    • Connect your profile if you haven't already
    • Browse the issues below and click \"Claim fix and test\" to get started
    • The fix and the test should be written in the same style of the project code.
    ", + "bonusPointsReminder": "Remember, you have 12 hours to submit after claiming. Happy fixing and testing!", "MyWallet": { "overview": "Overview", "pointValue": "Point value", diff --git a/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/VaultSubmissionsSection.tsx b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/VaultSubmissionsSection.tsx index a8451dc1b..f29d446ae 100644 --- a/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/VaultSubmissionsSection.tsx +++ b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/VaultSubmissionsSection.tsx @@ -1,5 +1,7 @@ import { IPayoutGraph, IVault } from "@hats.finance/shared"; +import ClockIcon from "@mui/icons-material/AccessTimeOutlined"; import OpenIcon from "@mui/icons-material/OpenInNewOutlined"; +import VerifiedIcon from "@mui/icons-material/VerifiedOutlined"; import { Alert, Button, HatSpinner } from "components"; import useConfirm from "hooks/useConfirm"; import { useTranslation } from "react-i18next"; @@ -38,6 +40,28 @@ export const VaultSubmissionsSection = ({ vault }: VaultSubmissionsSectionProps) window.open(githubLink, "_blank"); }; + const getBonusPointsSection = () => { + if (isPrivateAudit) return null; + if (savedSubmissions?.length === 0) return null; + + return ( +
    +
    +
    + +
    +

    {t("bonusPointsTitle")}

    +
    + +
    +
    + +

    {t("bonusPointsReminder")}

    +
    +
    + ); + }; + return ( {isPrivateAudit && ( @@ -45,6 +69,7 @@ export const VaultSubmissionsSection = ({ vault }: VaultSubmissionsSectionProps) {t("privateAuditSubmissionsOnlyOnGithub")} )} + {getBonusPointsSection()}

    {t("submissions")} diff --git a/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/styles.ts b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/styles.ts index 08171b6ac..13e1e67b3 100644 --- a/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/styles.ts +++ b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/styles.ts @@ -10,4 +10,44 @@ export const StyledSubmissionsSection = styled.div` flex-direction: column; gap: ${getSpacing(3)}; } + + .bonus-points-section { + border: 2px solid var(--primary); + padding: ${getSpacing(3)} ${getSpacing(4)}; + margin-bottom: ${getSpacing(4)}; + + .title-container { + display: flex; + align-items: center; + gap: ${getSpacing(1)}; + margin-bottom: ${getSpacing(2)}; + font-size: var(--moderate-big); + font-weight: 600; + + .icon { + display: flex; + align-items: center; + justify-content: center; + width: ${getSpacing(4.5)}; + height: ${getSpacing(4.5)}; + padding: ${getSpacing(1)}; + border-radius: 50%; + background-color: var(--primary); + } + } + + .content { + ul { + line-height: 1.5; + padding-left: ${getSpacing(3)}; + } + } + + .remember { + display: flex; + align-items: center; + margin-top: ${getSpacing(2)}; + gap: ${getSpacing(1)}; + } + } `; From 74f7606008d97853be3fcd28d4bad63604dbc7cc Mon Sep 17 00:00:00 2001 From: Carlos Fontes Date: Tue, 5 Nov 2024 13:25:26 +0000 Subject: [PATCH 16/25] chore: added `bonusPointsEnabled` functionality to vaults --- packages/shared/src/types/editor.ts | 1 + packages/shared/src/types/types.ts | 1 + .../src/components/VaultCard/VaultCard.tsx | 20 +++++++++++--- .../web/src/components/VaultCard/styles.ts | 12 ++++++--- packages/web/src/languages/en.json | 2 ++ .../PublicSubmissionCard.tsx | 9 ++++--- .../VaultSubmissionsSection.tsx | 4 ++- .../SubmissionDescriptions.tsx | 26 ++++++++++--------- .../SubmissionFormPage/SubmissionFormPage.tsx | 1 + .../submissionsService.api.ts | 3 +++ .../Submissions/SubmissionFormPage/types.ts | 1 + .../VaultDetailsForm/VaultDetailsForm.tsx | 9 +++++++ .../VaultEditorFormPage/formSchema.ts | 1 + 13 files changed, 68 insertions(+), 22 deletions(-) diff --git a/packages/shared/src/types/editor.ts b/packages/shared/src/types/editor.ts index 052ee0a4e..ce2d0bdc1 100644 --- a/packages/shared/src/types/editor.ts +++ b/packages/shared/src/types/editor.ts @@ -80,6 +80,7 @@ export interface IBaseEditedVaultDescription { isPrivateAudit?: boolean; isContinuousAudit?: boolean; requireMessageSignature?: boolean; + bonusPointsEnabled?: boolean; messageToSign?: string; whitelist: { address: string }[]; endtime?: number; diff --git a/packages/shared/src/types/types.ts b/packages/shared/src/types/types.ts index f216c5355..73b437641 100644 --- a/packages/shared/src/types/types.ts +++ b/packages/shared/src/types/types.ts @@ -141,6 +141,7 @@ interface IBaseVaultDescription { isPrivateAudit?: boolean; isContinuousAudit?: boolean; requireMessageSignature?: boolean; + bonusPointsEnabled?: boolean; messageToSign?: string; whitelist: { address: string }[]; endtime?: number; diff --git a/packages/web/src/components/VaultCard/VaultCard.tsx b/packages/web/src/components/VaultCard/VaultCard.tsx index 926668d38..1e4e7afe7 100644 --- a/packages/web/src/components/VaultCard/VaultCard.tsx +++ b/packages/web/src/components/VaultCard/VaultCard.tsx @@ -159,6 +159,8 @@ export const VaultCard = ({ ONE_LINER_FALLBACK[vault.id] ?? "Nulla facilisi. Donec nec dictum eros. Cras et velit viverra, dapibus velit fringilla, bibendum mi aptent. Class aptent taciti sociosqu ad litora."; + const bonusPointsEnabled = vault.description?.["project-metadata"]?.bonusPointsEnabled; + const getAuditStatusPill = () => { if (!vault.description) return null; if (!vault.description["project-metadata"].endtime) return null; @@ -470,9 +472,10 @@ export const VaultCard = ({ {!reducedStyles && (
    - {auditPayout ? t("paidAssets") : t("assetsInVault")} - - +
    + {auditPayout ? t("paidAssets") : t("assetsInVault")} + +
    @@ -487,6 +490,17 @@ export const VaultCard = ({ {t("deposits")} )} + {isAudit && vault.dateStatus === "on_time" && !auditPayout && !hideSubmit && bonusPointsEnabled && ( + + )} {(!isAudit || (isAudit && vault.dateStatus === "on_time" && !auditPayout)) && !hideSubmit && (
    - {showExtraInfo && (submission.bonusPointsLabels.needsFix || submission.bonusPointsLabels.needsTest) && ( - - )} + {showExtraInfo && + bonusPointsEnabled && + (submission.bonusPointsLabels.needsFix || submission.bonusPointsLabels.needsTest) && ( + + )}
    diff --git a/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/VaultSubmissionsSection.tsx b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/VaultSubmissionsSection.tsx index f29d446ae..814c6b7ec 100644 --- a/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/VaultSubmissionsSection.tsx +++ b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/VaultSubmissionsSection.tsx @@ -21,7 +21,9 @@ export const VaultSubmissionsSection = ({ vault }: VaultSubmissionsSectionProps) const { data: savedSubmissions, isLoading } = useSavedSubmissions(vault); const { data: repoName } = useVaultRepoName(vault); + const isPrivateAudit = vault?.description?.["project-metadata"].isPrivateAudit; + const bonusPointsEnabled = vault.description?.["project-metadata"]?.bonusPointsEnabled; const goToGithubIssues = async () => { if (!repoName) return; @@ -69,7 +71,7 @@ export const VaultSubmissionsSection = ({ vault }: VaultSubmissionsSectionProps) {t("privateAuditSubmissionsOnlyOnGithub")} )} - {getBonusPointsSection()} + {bonusPointsEnabled && getBonusPointsSection()}

    {t("submissions")} diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx index 27d374f28..b5e978a56 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx @@ -50,9 +50,9 @@ export function SubmissionDescriptions() { const isAuditSubmission = vault?.description?.["project-metadata"].type === "audit"; const isPrivateAudit = vault?.description?.["project-metadata"].isPrivateAudit; + const bonusPointsEnabled = vault?.description?.["project-metadata"].bonusPointsEnabled; const { data: claimedIssues, isLoading: isLoadingClaimedIssues } = useClaimedIssuesByVaultAndClaimedBy(vault, address); - console.log(claimedIssues); const [vaultGithubIssuesOpts, setVaultGithubIssuesOpts] = useState(); const [vaultGithubIssues, setVaultGithubIssues] = useState(undefined); @@ -611,21 +611,23 @@ export function SubmissionDescriptions() { )}

    -
    -
    setValue(`descriptions.${index}.type`, "new")}> -
    -
    -

    {t("newSubmission")}

    + {bonusPointsEnabled && ( +
    +
    setValue(`descriptions.${index}.type`, "new")}> +
    +
    +

    {t("newSubmission")}

    +
    -
    -
    setValue(`descriptions.${index}.type`, "complement")}> -
    -
    -

    {t("complementSubmission")}

    +
    setValue(`descriptions.${index}.type`, "complement")}> +
    +
    +

    {t("complementSubmission")}

    +
    -
    + )} {submissionDescription.type === "new" ? getNewIssueForm(submissionDescription, index) diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/SubmissionFormPage.tsx b/packages/web/src/pages/Submissions/SubmissionFormPage/SubmissionFormPage.tsx index 42914fe55..9a354272b 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/SubmissionFormPage.tsx +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/SubmissionFormPage.tsx @@ -54,6 +54,7 @@ export const SubmissionFormPage = () => { const vault = (activeVaults ?? []).find((vault) => vault.id === submissionData?.project?.projectId); const requireMessageSignature = vault?.description?.["project-metadata"].requireMessageSignature; + const { data: userHasCollectedSignature, isLoading: isLoadingCollectedSignature } = useUserHasCollectedSignature(vault); const steps = useMemo( diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/submissionsService.api.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/submissionsService.api.ts index deb5a6d5d..e5629f423 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/submissionsService.api.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/submissionsService.api.ts @@ -21,6 +21,8 @@ export async function submitVulnerabilitySubmission( throw new Error(`Invalid params on 'submitVulnerabilitySubmission' function: ${submissionData}`); } + const bonusPointsEnabled = vault?.description?.["project-metadata"]?.bonusPointsEnabled; + const submissionRequest: ISubmitSubmissionRequest = { submitVulnerabilityRequest: { chainId: submissionData.submissionResult.chainId, @@ -38,6 +40,7 @@ export async function submitVulnerabilitySubmission( issueDescription: getGithubIssueDescription(submissionData, description, hackerProfile), issueFiles: description.files?.map((file) => file.ipfsHash), isTestApplicable: description.isTestApplicable, + bonusPointsEnabled, })) : [], createPRsRequests: diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts index 9b05f8972..6adc99eba 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/types.ts @@ -95,6 +95,7 @@ export interface ISubmitSubmissionRequest { issueDescription: string; issueFiles: string[]; isTestApplicable?: boolean; + bonusPointsEnabled?: boolean; }[]; createPRsRequests: { pullRequestTitle: string; diff --git a/packages/web/src/pages/VaultEditor/VaultEditorFormPage/SetupSteps/VaultDetailsForm/VaultDetailsForm.tsx b/packages/web/src/pages/VaultEditor/VaultEditorFormPage/SetupSteps/VaultDetailsForm/VaultDetailsForm.tsx index 437a5e228..6eb9976b8 100644 --- a/packages/web/src/pages/VaultEditor/VaultEditorFormPage/SetupSteps/VaultDetailsForm/VaultDetailsForm.tsx +++ b/packages/web/src/pages/VaultEditor/VaultEditorFormPage/SetupSteps/VaultDetailsForm/VaultDetailsForm.tsx @@ -159,6 +159,15 @@ export function VaultDetailsForm() { label={t("requireMessageSignature")} /> ) : null} + {vaultType === "audit" && ( + + )}
    diff --git a/packages/web/src/pages/VaultEditor/VaultEditorFormPage/formSchema.ts b/packages/web/src/pages/VaultEditor/VaultEditorFormPage/formSchema.ts index f440d22e4..f22d3cbeb 100644 --- a/packages/web/src/pages/VaultEditor/VaultEditorFormPage/formSchema.ts +++ b/packages/web/src/pages/VaultEditor/VaultEditorFormPage/formSchema.ts @@ -27,6 +27,7 @@ export const getEditedDescriptionYupSchema = (intl: TFunction) => isPrivateAudit: Yup.boolean(), isContinuousAudit: Yup.boolean(), requireMessageSignature: Yup.boolean(), + bonusPointsEnabled: Yup.boolean(), messageToSign: Yup.string().when("requireMessageSignature", (requireMessageSignature: boolean, schema: any) => { if (requireMessageSignature) return schema.required(intl("required")).typeError(intl("required")); }), From 40b8fd1f6f9caf6296a20b15a674e62543f5641d Mon Sep 17 00:00:00 2001 From: Carlos Fontes Date: Wed, 6 Nov 2024 10:55:16 +0000 Subject: [PATCH 17/25] chore: now `bonusPointEnabled` true by default + only in advanced mode --- .../SetupSteps/VaultDetailsForm/VaultDetailsForm.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/web/src/pages/VaultEditor/VaultEditorFormPage/SetupSteps/VaultDetailsForm/VaultDetailsForm.tsx b/packages/web/src/pages/VaultEditor/VaultEditorFormPage/SetupSteps/VaultDetailsForm/VaultDetailsForm.tsx index 6eb9976b8..2ac9c2b0d 100644 --- a/packages/web/src/pages/VaultEditor/VaultEditorFormPage/SetupSteps/VaultDetailsForm/VaultDetailsForm.tsx +++ b/packages/web/src/pages/VaultEditor/VaultEditorFormPage/SetupSteps/VaultDetailsForm/VaultDetailsForm.tsx @@ -99,6 +99,10 @@ export function VaultDetailsForm() { if (isAudit) setValue("usingPointingSystem", true); else setValue("usingPointingSystem", false); trigger("vulnerability-severities-spec"); + + // Change the bonus points enabled value + if (vaultType === "audit") setValue("project-metadata.bonusPointsEnabled", true); + else setValue("project-metadata.bonusPointsEnabled", false); }); return ( @@ -159,7 +163,7 @@ export function VaultDetailsForm() { label={t("requireMessageSignature")} /> ) : null} - {vaultType === "audit" && ( + {isAdvancedMode && vaultType === "audit" && ( Date: Wed, 6 Nov 2024 10:58:19 +0000 Subject: [PATCH 18/25] chore: added code style alert --- packages/web/src/languages/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/src/languages/en.json b/packages/web/src/languages/en.json index 850f64c31..8de73b919 100644 --- a/packages/web/src/languages/en.json +++ b/packages/web/src/languages/en.json @@ -741,7 +741,7 @@ "youNeedToConnectYourWallet": "You need to connect your wallet.", "youAreNotInTopLeaderboardPercentage": "This action is reserved for participants in the top of HATS leaderboard. Improve your ranking to unlock or connect with your profile.", "claimIssue": "Claim issue", - "claimIssueDescription": "Are you sure you want to claim this issue?", + "claimIssueDescription": "-CODE STYLE ALERT-\n\nPlease note you need to write the fix and the test in the same style of the project code.", "claimedByYou": "Claimed by you", "issueAlreadyClaimed": "This issue has already been claimed.", "bonusPointsTitle": "New: Boost Your Rewards, Claim and Submit Fixes and Tests!", From 36b9c9c9fc8f0432fa3884fdfea4ca1a87dbe3cd Mon Sep 17 00:00:00 2001 From: Carlos Fontes Date: Thu, 7 Nov 2024 10:57:47 +0000 Subject: [PATCH 19/25] chore: added bonus points info --- .../VaultRewardsSection.tsx | 21 +++++++++++- .../Sections/VaultRewardsSection/styles.ts | 34 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultRewardsSection/VaultRewardsSection.tsx b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultRewardsSection/VaultRewardsSection.tsx index 8e9857828..21fa430b4 100644 --- a/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultRewardsSection/VaultRewardsSection.tsx +++ b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultRewardsSection/VaultRewardsSection.tsx @@ -3,7 +3,6 @@ import { VaultAssetsPillsList, WithTooltip } from "components"; import millify from "millify"; import { useTranslation } from "react-i18next"; import { formatNumber } from "utils"; -import { VaultNftRewards } from "./VaultNftRewards/VaultNftRewards"; import { VaultRewardDivision } from "./VaultRewardDivision/VaultRewardDivision"; import { VaultSeverityRewards } from "./VaultSeverityRewards/VaultSeverityRewards"; import { StyledRewardsSection } from "./styles"; @@ -18,6 +17,7 @@ export const VaultRewardsSection = ({ vault }: VaultRewardsSectionProps) => { const { t } = useTranslation(); const isAudit = vault.description && vault.description["project-metadata"].type === "audit"; + const bonusPointsEnabled = (vault.description && vault.description["project-metadata"].bonusPointsEnabled) ?? false; const showIntended = vault.amountsInfo?.showCompetitionIntendedAmount ?? false; return ( @@ -90,6 +90,25 @@ export const VaultRewardsSection = ({ vault }: VaultRewardsSectionProps) => {

    {t("severityRewards")}

    + {bonusPointsEnabled && ( +
    +

    Bonuses

    +
    +
    + Fix: + + 10% of issue points + +
    +
    + Test: + + 5% of issue points + +
    +
    +
    + )}
    diff --git a/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultRewardsSection/styles.ts b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultRewardsSection/styles.ts index b052d86f3..5dd2cf8b1 100644 --- a/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultRewardsSection/styles.ts +++ b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultRewardsSection/styles.ts @@ -67,6 +67,40 @@ export const StyledRewardsSection = styled.div<{ showIntended: boolean; isAudit: grid-column-start: 1; grid-column-end: 3; } + + .bonus-points-info-container { + border-top: 1px solid var(--primary-light); + width: 100%; + display: flex; + flex-direction: column; + gap: ${getSpacing(1)}; + padding-top: ${getSpacing(3)}; + + p { + font-weight: 700; + } + + .points { + display: flex; + gap: ${getSpacing(2)}; + + .point { + display: flex; + align-items: center; + gap: ${getSpacing(1)}; + + .secondary-text { + color: var(--secondary); + font-weight: 700; + } + + .primary-text { + color: var(--primary); + font-weight: 700; + } + } + } + } } .card { From 71d9fb3bfc56ae746c5dda1e955131e4d03c6822 Mon Sep 17 00:00:00 2001 From: Carlos Fontes Date: Thu, 7 Nov 2024 13:55:35 +0000 Subject: [PATCH 20/25] fix --- .../SubmissionDescriptions/SubmissionDescriptions.tsx | 10 ++++++++-- .../FormSteps/SubmissionDescriptions/formSchema.ts | 5 ++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx index b5e978a56..a8479080b 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx @@ -303,14 +303,20 @@ export function SubmissionDescriptions() {

    Does this issue needs a fix?

    -
    setValue(`descriptions.${index}.isTestApplicable`, true)}> +
    setValue(`descriptions.${index}.isTestApplicable`, true, { shouldValidate: true })} + >

    {t("Submissions.pocIsApplicable")}

    -
    setValue(`descriptions.${index}.isTestApplicable`, false)}> +
    setValue(`descriptions.${index}.isTestApplicable`, false, { shouldValidate: true })} + >

    {t("Submissions.pocIsNotApplicable")}

    diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts index ef66054e8..009781f27 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts @@ -22,7 +22,10 @@ export const getCreateDescriptionSchema = (intl: TFunction) => if (type === "complement") return schema; return schema.required(intl("required")); }), - isTestApplicable: Yup.boolean().required(intl("required")), + isTestApplicable: Yup.boolean().when("type", (type: "new" | "complement", schema: any) => { + if (type === "new") return schema.required(intl("required")); + return schema; + }), // complement fields needsFix: Yup.boolean(), From 7343ffef533a7f15ac508eeafebfbd7a14e2e80d Mon Sep 17 00:00:00 2001 From: Shay Zluf Date: Fri, 8 Nov 2024 07:14:48 +0200 Subject: [PATCH 21/25] Update packages/web/src/components/FormControls/FormSupportFilesInput/supportedExtensions.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../FormControls/FormSupportFilesInput/supportedExtensions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/src/components/FormControls/FormSupportFilesInput/supportedExtensions.ts b/packages/web/src/components/FormControls/FormSupportFilesInput/supportedExtensions.ts index 531ba226c..063113c31 100644 --- a/packages/web/src/components/FormControls/FormSupportFilesInput/supportedExtensions.ts +++ b/packages/web/src/components/FormControls/FormSupportFilesInput/supportedExtensions.ts @@ -1 +1 @@ -export const supportedExtensions = ["txt", "sol", "ts", "js", ".md", ".json"]; +export const supportedExtensions = ["txt", "sol", "ts", "js", "md", "json"]; From 2472edde78fe36833c066b9c76db84f34763938e Mon Sep 17 00:00:00 2001 From: Carlos Fontes Date: Fri, 8 Nov 2024 09:39:26 +0000 Subject: [PATCH 22/25] chore: improved error handling in --- packages/web/src/utils/github.utils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/web/src/utils/github.utils.ts b/packages/web/src/utils/github.utils.ts index fdd171da9..d9c6cb770 100644 --- a/packages/web/src/utils/github.utils.ts +++ b/packages/web/src/utils/github.utils.ts @@ -7,12 +7,14 @@ export const isGithubUsernameValid = async (username: string) => { }; export const searchFileInHatsRepo = async (repoName: string, fileName: string): Promise => { + if (!repoName || !fileName) return []; + try { const res = await axiosClient.get(`${BASE_SERVICE_URL}/utils/search-file-in-repo?repoName=${repoName}&fileName=${fileName}`); return res.data.files ?? []; } catch (err) { console.error(err); - throw new Error(`Error getting prices: ${err}`); + return []; } }; From 19baa4117540f806bef66ccbe2b7de2f0c338962 Mon Sep 17 00:00:00 2001 From: Carlos Fontes Date: Fri, 8 Nov 2024 09:42:20 +0000 Subject: [PATCH 23/25] chore: fixed submission formSchema --- .../FormSteps/SubmissionDescriptions/formSchema.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts index 009781f27..a909197ab 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/formSchema.ts @@ -16,12 +16,10 @@ export const getCreateDescriptionSchema = (intl: TFunction) => if (type === "complement") return schema; return schema.required(intl("required")); }), - description: Yup.string() - .min(20, intl("min-characters", { min: 20 })) - .when("type", (type: "new" | "complement", schema: any) => { - if (type === "complement") return schema; - return schema.required(intl("required")); - }), + description: Yup.string().when("type", (type: "new" | "complement", schema: any) => { + if (type === "complement") return schema; + return schema.min(20, intl("min-characters", { min: 20 })).required(intl("required")); + }), isTestApplicable: Yup.boolean().when("type", (type: "new" | "complement", schema: any) => { if (type === "new") return schema.required(intl("required")); return schema; From 9edcab07b9dccd7d451caa5af4117d9a22b5f80e Mon Sep 17 00:00:00 2001 From: Carlos Fontes Date: Fri, 8 Nov 2024 09:43:25 +0000 Subject: [PATCH 24/25] chore: comment rolledback --- .../PublicSubmissionCard/components/SplitPointsActions.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/components/SplitPointsActions.tsx b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/components/SplitPointsActions.tsx index 654eb7360..6a4ac265b 100644 --- a/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/components/SplitPointsActions.tsx +++ b/packages/web/src/pages/Honeypots/VaultDetailsPage/Sections/VaultSubmissionsSection/PublicSubmissionCard/components/SplitPointsActions.tsx @@ -64,8 +64,7 @@ export const SplitPointsActions = ({ vault, submission }: SplitPointsActionsProp const claimedByInfo = getClaimedBy(claimInfo); const { data: claimedByProfile } = useProfileByAddress(claimedByInfo?.claimedBy); - // const isClaimedByCurrentUser = claimedByInfo?.claimedBy.toLowerCase() === address?.toLowerCase(); - const isClaimedByCurrentUser = false; + const isClaimedByCurrentUser = claimedByInfo?.claimedBy.toLowerCase() === address?.toLowerCase(); const canExecuteAction = () => { if (isClaimedByCurrentUser) return { can: true }; From 870cd74aa6262077e17c8236f1d9c31e53a36759 Mon Sep 17 00:00:00 2001 From: Carlos Fontes Date: Fri, 8 Nov 2024 09:46:15 +0000 Subject: [PATCH 25/25] chore: removed log --- .../SubmissionDescriptions/SubmissionDescriptions.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx index a8479080b..71a1d680e 100644 --- a/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx +++ b/packages/web/src/pages/Submissions/SubmissionFormPage/FormSteps/SubmissionDescriptions/SubmissionDescriptions.tsx @@ -217,12 +217,6 @@ export function SubmissionDescriptions() { `${submissionData.project?.projectName}-${new Date().getTime()}.json` ); - console.log({ - submissionMessage, - submissionInfo: JSON.stringify(submissionInfo), - descriptions: formData.descriptions, - }); - setSubmissionData((prev) => { if (!prev) return prev; return {